diff --git a/.fbprefs b/.fbprefs new file mode 100644 index 0000000..3572082 --- /dev/null +++ b/.fbprefs @@ -0,0 +1,129 @@ +#FindBugs User Preferences +#Tue Sep 20 20:18:08 EDT 2011 +detectorAppendingToAnObjectOutputStream=AppendingToAnObjectOutputStream|true +detectorBadAppletConstructor=BadAppletConstructor|false +detectorBadResultSetAccess=BadResultSetAccess|true +detectorBadSyntaxForRegularExpression=BadSyntaxForRegularExpression|true +detectorBadUseOfReturnValue=BadUseOfReturnValue|true +detectorBadlyOverriddenAdapter=BadlyOverriddenAdapter|true +detectorBooleanReturnNull=BooleanReturnNull|true +detectorCallToUnsupportedMethod=CallToUnsupportedMethod|false +detectorCheckImmutableAnnotation=CheckImmutableAnnotation|true +detectorCheckTypeQualifiers=CheckTypeQualifiers|true +detectorCloneIdiom=CloneIdiom|true +detectorComparatorIdiom=ComparatorIdiom|true +detectorConfusedInheritance=ConfusedInheritance|true +detectorConfusionBetweenInheritedAndOuterMethod=ConfusionBetweenInheritedAndOuterMethod|true +detectorCrossSiteScripting=CrossSiteScripting|true +detectorDoInsideDoPrivileged=DoInsideDoPrivileged|true +detectorDontCatchIllegalMonitorStateException=DontCatchIllegalMonitorStateException|true +detectorDontIgnoreResultOfPutIfAbsent=DontIgnoreResultOfPutIfAbsent|true +detectorDontUseEnum=DontUseEnum|true +detectorDroppedException=DroppedException|true +detectorDumbMethodInvocations=DumbMethodInvocations|true +detectorDumbMethods=DumbMethods|true +detectorDuplicateBranches=DuplicateBranches|true +detectorEmptyZipFileEntry=EmptyZipFileEntry|true +detectorEqualsOperandShouldHaveClassCompatibleWithThis=EqualsOperandShouldHaveClassCompatibleWithThis|true +detectorFinalizerNullsFields=FinalizerNullsFields|true +detectorFindBadCast2=FindBadCast2|true +detectorFindBadForLoop=FindBadForLoop|true +detectorFindCircularDependencies=FindCircularDependencies|false +detectorFindDeadLocalStores=FindDeadLocalStores|true +detectorFindDoubleCheck=FindDoubleCheck|true +detectorFindEmptySynchronizedBlock=FindEmptySynchronizedBlock|true +detectorFindFieldSelfAssignment=FindFieldSelfAssignment|true +detectorFindFinalizeInvocations=FindFinalizeInvocations|true +detectorFindFloatEquality=FindFloatEquality|true +detectorFindHEmismatch=FindHEmismatch|true +detectorFindInconsistentSync2=FindInconsistentSync2|true +detectorFindJSR166LockMonitorenter=FindJSR166LockMonitorenter|true +detectorFindLocalSelfAssignment2=FindLocalSelfAssignment2|true +detectorFindMaskedFields=FindMaskedFields|true +detectorFindMismatchedWaitOrNotify=FindMismatchedWaitOrNotify|true +detectorFindNakedNotify=FindNakedNotify|true +detectorFindNonSerializableStoreIntoSession=FindNonSerializableStoreIntoSession|true +detectorFindNonSerializableValuePassedToWriteObject=FindNonSerializableValuePassedToWriteObject|true +detectorFindNonShortCircuit=FindNonShortCircuit|true +detectorFindNullDeref=FindNullDeref|true +detectorFindNullDerefsInvolvingNonShortCircuitEvaluation=FindNullDerefsInvolvingNonShortCircuitEvaluation|true +detectorFindOpenStream=FindOpenStream|true +detectorFindPuzzlers=FindPuzzlers|true +detectorFindRefComparison=FindRefComparison|true +detectorFindReturnRef=FindReturnRef|true +detectorFindRunInvocations=FindRunInvocations|true +detectorFindSelfComparison=FindSelfComparison|true +detectorFindSelfComparison2=FindSelfComparison2|true +detectorFindSleepWithLockHeld=FindSleepWithLockHeld|true +detectorFindSpinLoop=FindSpinLoop|true +detectorFindSqlInjection=FindSqlInjection|true +detectorFindTwoLockWait=FindTwoLockWait|true +detectorFindUncalledPrivateMethods=FindUncalledPrivateMethods|true +detectorFindUnconditionalWait=FindUnconditionalWait|true +detectorFindUninitializedGet=FindUninitializedGet|true +detectorFindUnrelatedTypesInGenericContainer=FindUnrelatedTypesInGenericContainer|true +detectorFindUnreleasedLock=FindUnreleasedLock|true +detectorFindUnsatisfiedObligation=FindUnsatisfiedObligation|true +detectorFindUnsyncGet=FindUnsyncGet|true +detectorFindUselessControlFlow=FindUselessControlFlow|true +detectorFormatStringChecker=FormatStringChecker|true +detectorHugeSharedStringConstants=HugeSharedStringConstants|true +detectorIDivResultCastToDouble=IDivResultCastToDouble|true +detectorIncompatMask=IncompatMask|true +detectorInconsistentAnnotations=InconsistentAnnotations|true +detectorInefficientMemberAccess=InefficientMemberAccess|false +detectorInefficientToArray=InefficientToArray|true +detectorInfiniteLoop=InfiniteLoop|true +detectorInfiniteRecursiveLoop=InfiniteRecursiveLoop|true +detectorInfiniteRecursiveLoop2=InfiniteRecursiveLoop2|false +detectorInheritanceUnsafeGetResource=InheritanceUnsafeGetResource|true +detectorInitializationChain=InitializationChain|true +detectorInstantiateStaticClass=InstantiateStaticClass|true +detectorInvalidJUnitTest=InvalidJUnitTest|true +detectorIteratorIdioms=IteratorIdioms|true +detectorLazyInit=LazyInit|true +detectorLoadOfKnownNullValue=LoadOfKnownNullValue|true +detectorLostLoggerDueToWeakReference=LostLoggerDueToWeakReference|true +detectorMethodReturnCheck=MethodReturnCheck|true +detectorMultithreadedInstanceAccess=MultithreadedInstanceAccess|true +detectorMutableLock=MutableLock|true +detectorMutableStaticFields=MutableStaticFields|true +detectorNaming=Naming|true +detectorNumberConstructor=NumberConstructor|true +detectorOverridingEqualsNotSymmetrical=OverridingEqualsNotSymmetrical|true +detectorPreferZeroLengthArrays=PreferZeroLengthArrays|true +detectorPublicSemaphores=PublicSemaphores|false +detectorQuestionableBooleanAssignment=QuestionableBooleanAssignment|true +detectorReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass=ReadOfInstanceFieldInMethodInvokedByConstructorInSuperclass|true +detectorReadReturnShouldBeChecked=ReadReturnShouldBeChecked|true +detectorRedundantInterfaces=RedundantInterfaces|true +detectorRepeatedConditionals=RepeatedConditionals|true +detectorRuntimeExceptionCapture=RuntimeExceptionCapture|true +detectorSerializableIdiom=SerializableIdiom|true +detectorStartInConstructor=StartInConstructor|true +detectorStaticCalendarDetector=StaticCalendarDetector|true +detectorStringConcatenation=StringConcatenation|true +detectorSuperfluousInstanceOf=SuperfluousInstanceOf|true +detectorSuspiciousThreadInterrupted=SuspiciousThreadInterrupted|true +detectorSwitchFallthrough=SwitchFallthrough|true +detectorSynchronizeAndNullCheckField=SynchronizeAndNullCheckField|true +detectorSynchronizeOnClassLiteralNotGetClass=SynchronizeOnClassLiteralNotGetClass|true +detectorSynchronizingOnContentsOfFieldToProtectField=SynchronizingOnContentsOfFieldToProtectField|true +detectorURLProblems=URLProblems|true +detectorUncallableMethodOfAnonymousClass=UncallableMethodOfAnonymousClass|true +detectorUnnecessaryMath=UnnecessaryMath|true +detectorUnreadFields=UnreadFields|true +detectorUseObjectEquals=UseObjectEquals|false +detectorUselessSubclassMethod=UselessSubclassMethod|false +detectorVarArgsProblems=VarArgsProblems|true +detectorVolatileUsage=VolatileUsage|true +detectorWaitInLoop=WaitInLoop|true +detectorWrongMapIterator=WrongMapIterator|true +detectorXMLFactoryBypass=XMLFactoryBypass|true +detector_threshold=2 +effort=default +excludefilter0=FindBugsExcludeFilter.xml +filter_settings=Medium|BAD_PRACTICE,CORRECTNESS,MT_CORRECTNESS,PERFORMANCE,STYLE|false +filter_settings_neg=MALICIOUS_CODE,NOISE,I18N,SECURITY,EXPERIMENTAL| +includefilter0=FindBugsIncludeFilter.xml +run_at_full_build=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0bc2e23 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +/.classpath +/.project +/.idea/inspectionProfiles +/ebuild +/.settings +/build +/cobertura.ser +/build.properties +/bin diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..40ed937 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..d8a0065 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..51d7d3b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..1408508 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,1407 @@ +!!! This file is no longer maintained. Please see the GitHub Commits !!! +https://github.com/hunterhacker/jdom/commits/master + + +* * * * * * JDOM 1.1.1 (tag: jdom_1_1_1) from JDOM 1.1 * * * * * * + +Fixed a synchronization issue in the Namespace class that could cause a +hang when doing concurrent builds. + +Added output support for Unicode surrogate pairs. + +Added a new flag on SAXBuilder named setFastReconfigure() which, when set, +can speed reconfiguration by skipping repeated attempts to set features that +are determined not to be present on a parser. Useful when doing many builds +per second. + +Updated the provided Jaxen library from a modified Jaxen 1.0 to the latest +which is Jaxen 1.1.1. + +Added reflection code in the error reporting system to support Android's +Dalvik VM which doesn't have the java.rmi.* classes. + + +* * * * * * JDOM 1.1 (tag: jdom_1_1) from JDOM 1.0 * * * * * * + +Added an additional constructor to JDOMSource with an EntityResolver which is +passed to the internal DocumentReader allowing the SAXOutputter to properly +resolve DTDs. + +Added a forceNamespaceAware property to DOMOutputter which specifies you want +a DOM constructed with namespaces even if the source JDOM document has no +namespaces. + +Added support for attribute "INF" and "-INF" values, to indicate positive and +negative infinity, as XML Schema allows. + +Moved isXMLWhitespace() method from private in XMLOutputter to public in +Verifier. + +Clarified XMLOutputter behavior with newlines and indents: + setIndent(" ") means newlines and " " indents + setIndent("") means newlines and "" indents + setIndent(null) means no newlines and no indents + +Added set/getIgnoringBoundaryWhitespace() methods and features to SAXBuilder +and SAXHandler. + +Added a setFactory() method on XSLTransformer to control the object types +built by the transform. + +Added a string constant for the JDOM_OBJECT_MODEL_URI used by JAXP 1.3. It +deserves being part of the public API. + +Fixed bug in SAXOutputter where default namespaces would be declared as +xmlns:="" with a spurious colon. + +Fixed bug when using attributes without a namespace and outputting to a +JDOMResult. + +Removing check that a comment not start with a hyphen. A careful reading of +production 15 in the XML 1.0 spec indicates leading hyphens are in fact +allowed. + +Fixed bug where outputFragment() on SAXOutputter could cause a +NullPointerException because the locator would be null during the call. + +Fixed bug where serializing ElementFilter causes a NullPointerException if the +filter has no assigned namespace + +Fixed some subtle bad behaviors in listIterator.add() logic, using brand new +iterator logic. + +Allowed a String to be passed to ContentList.add(int, Object). + +Simplified JDOMAbout and renamed info.xml to jdom-info.xml, so +getResourceAsStream() won't suffer any name collision. + +Fixed tiny issue where CDATA could be set with illegal character content. + +Added logic to escape some special characters in namespace URIs. + +Fixed bug where the attribute type would change on a setAttribute() call. + +Improved performance on Namespace handling. + +Improved and clarified Javadocs. + + +* * * * * * JDOM 1.0 (tag: jdom_1_0) from JDOM Beta10 * * * * * * + +Added a new lib/jaxen-jdom.jar that solves some XPath ancestry problems +introduced by the Parent interface. See the new lib/jaxen.readme for +details. + +Moved the addContent() and setContent() methods from Parent into Element and +Document directly. This re-enables method chaining that some people missed. + +Fixed a few bugs in SAXOutputter: start/endPrefixMapping was not being fired +for no namespace, DocType was being improperly constructed, changed to use a +DefaultHandler with the dtd parser to better suppress unimportant problems. + +Added SAXOutputter support for outputting fragments of documents with the +new methods: + output(Content) + outputFragment(List) + outputFragment(Content) + +Added support in XMLOutputter for ignoring the JAXP processing instructions + and +. Respect for these PIs is +toggled by the Format.set/getIgnoreTrAXEscapingPIs() feature, default false. + +Added to JDOMFactory the methods document(Element rootElement, DocType +docType, String baseURI) and entityRef(String elementName, String systemID). +These match constructors that were previously overlooked. Also added +implementations to DefaultJDOMFactory and UnverifiedJDOMFactory. + +Added to Element the method getParentElement() that returns the parent element +or null if the object is unattached or the root element. + +Fixed bug in FilterIterator that affected next() calls. + +Fixed bug in DOMOutputter regarding extraneous namespace declarations +appearing under certain conditions. + +Changed XMLOutputter to clone the Format objects when they're set/get + +Fixed bug in JDOMResult where the result list could include incomplete +results in certain situations, fixed by forcing a flush. + +Made SAXHandler.flushCharacters() protected again after being private. It's +needed for the above fix. + +Changed Verifier.isXXX() methods from private to public as it was well +argued that they're based on unchanging spec productions and can be +generally useful even apart from JDOM. + +Added support for surrogate pairs in the Verifier. (Surrogate pairs don't +yet have any special output support.) + +Fixed bug in SAXBuilder to avoid an IllegalStateException when apps access +the partial document when parse failure occurs right after the beginning of +the parse. + +Updated JaxenXPath to avoid the deprecated XPath.valueOf(). + +Brought the jdom-contrib ElementScanner up to date. + +Fixed various Javadoc typos. + +Removed the build-time dependence on saxpath.jar. + +Removed all deprecated methods. + +Fixed bug where in "pretty print" output EntityRef instances would erroneously +print on their own line. + +Added character encoding rules to improve whitespace round tripping: + http://lists.denveronline.net/lists/jdom-interest/2003-July/013227.html + +Added DOMBuilder.getFactory() method to match what we added to SAXBuilder. + +Removed Parent.canContain() and the Document and Element implementations. +Moved the logic directly into ContentList. No reason to expose a public +method unless it has a general purpose. + +Reduced the visibility on some XMLOutputter internals that we don't want to +guarantee support for over the long term. Some people who want custom output +formatting may need to copy some code blocks. That's OK since JDOM is open +source and while it's less than ideal, it's better than our exposing protected +internal variables and methods that we may have reason to change later. + Now marked private: + userFormat + printString() + printContentRange() + printTextRange() + Now static final: + preserveFormat + +Removed some unnecessary casts. + +Made a few private methods static where it made sense. Also a select +few protected methods. + +Made the Format constructor private. It used to be pkg protected which +doesn't make a lot of sense. + +Removed equals() from AbstractFilter since it's better to let concrete +subclasses define that, as they already were. + +Removed some unnecessary instanceof and != null checks. + +-- +Added an UncheckedJDOMFactory class which builds without doing any +content or structure checks, letting you gain speed in a situation +where you have 100% confidence in your parser. The Javadocs for +the class naturally includes a serious warning. + +It's not used by default, but you can select it with a +builder.setFactory(new UncheckedJDOMFactory()) call. + +I also added to JDOMFactory a few methods: + addContent(Parent, Content) + setAttribute(Element, Attribute) + addNamespaceDeclaration(Element, Namespace) + +These are called during the build to do the adds. The default builder +just calls parent.addContent(Content) while the "unchecked" factory does +the work without checks using package protected methods on ContentList +and AttributeList. + +A perk of having these methods in the factory and used by the builder +is you can write a custom factory to do certain things during the adds. +Like you could ignore all elements named "foo" by not doing the add if +the Content was an Elt foo. That's not perfect since the elements +underneath foo would still be built into a subtree that got ignored, +but it's an easy solution to save memory in the resulting document. +-- + +* * * * * * Beta10 (tag: jdom_1_0_b10) from Beta9 * * * * * * + +PARENT AND CONTENT +------------------ + +Added a new Parent interface and a new Content abstract class. Parent is +implemented by Document and Element. Content is extended by Element, Comment, +DocType, EntityRef, ProcessingInstruction, and Text (CDATA). + +Parent has methods (* means new): + Parent addContent(Content child); + * Parent addContent(Collection collection); + * Parent addContent(int index, Content child); + * Parent addContent(int index, Collection collection); + * List cloneContent(); + * void canContain(Content, int); + List getContent(); + List getContent(Filter filter); + * Content getContent(int index); + * int getContentSize(); + * Iterator getDescendants() + * Iterator getDescendants(Filter) + * Document getDocument() + * Parent getParent() + * int indexOf(Content) + * List removeContent(); + boolean removeContent(Content child); + * List removeContent(Filter filter); + * Content removeContent(int index); + Parent setContent(Content child); + Parent setContent(Collection collection); + * Parent setContent(int index, Content child); + * Parent setContent(int index, Collection collection); + Object clone(); + +Content has public methods: + Content detach(); + Document getDocument(); + Parent getParent(); + * String getValue(); + Object clone(); + +The new methods on Parent are pretty self explanatory. A few methods that used +to require getting the content List now work on Parent itself for convenience +(tired of all those FAQs). The cloneContent() and removeContent() calls +should be especially helpful. The getDescendants() methods is great in +providing a mechanism to walk the entire tree from this item down using an +optional filter. + +The getValue() method in Content is defined to return the XPath 1.0 string +value of the element. The getText() methods in Element are left unchanged. + +A subtle change is that getParent() now returns a Parent type which is its +immediate parent. Previously an item at the document level would return null +and you'd use getDocument() to get its Document. Parent has getParent() as +well to make repeated getParent() calls easier. + +The protected setDocument() methods have been removed in favor of just using +setParent(). getDocument() remains as a potentially recursive lookup method. + + +NEW CLASSES +----------- + +Added an org.jdom.transform.XSLTransformer class to help with simple +transformations. It's a one-liner now, the way it should be. Also added an +XSLTransformException class to support the XSLTransformer. + +Added an org.jdom.output.Format class to control XMLOutputter behavior. +Format has convenience methods .getRawFormat(), .getPrettyFormat(), and +.getCompactFormat() to use in lieu of people having to remember when to trim, +set indents, and such. The old XMLOutputter.set*() methods are now deprecated +and should be called on a Format instance. The XMLOutputter constructors that +took indents and so on are also deprecated. + +Added an EscapeStrategy plug-in interface for XMLOutputter to determine which +chars to escape. A user can set a strategy and go, no need to subclass. + +Created a DefaultEscapeStrategy which tries to be generally smart. It quickly +says no escaping is necessary for UTF-8 (our default) and UTF-16. It escapes +everything above 255 for ISO-8859-1/Latin1. It escapes everything above 127 +for ASCII. For the other charsets, it tries to use the JDK 1.4 CharsetEncoder +to determine if the char needs escaping. Reflection is used for this so JDOM +isn't dependent on JDK 1.4. That means if you run on JDK 1.3 there's no +escaping unless JDOM knows about the charset itself or you plug in your own. + +Added a Format.TextMode inner class with values: PRESERVE, TRIM, NORMALIZE, +and TRIM_FULL_WHITE. Removed the methods setTextTrim(), setTextNormalize(), +and setTrimAllWhite(). Replaced them with setTextMode(Format.TextMode) and +getTextMode(). + +Moved org.jdom.input.JDOMFactory and org.jdom.input.DefaultJDOMFactory into +the org.jdom package. + + +NEW METHODS +----------- + +Added Document.setBaseURI(String) and getBaseURI() to record the effective URI +from which the document was loaded (against which relative URLs in the +document should be resolved). The builders record the URI when possible. + +Added a Document(Element, DocType, String baseURI) constructor. + + +ENHANCEMENTS +------------ + +Incorporated Jaxen 1.0 and Xerces 2.6.1. + +Enhanced the filter classes so they extend a new AbstractFilter class and +inherit its and(), or(), and negate() methods. + +Added proper hashCode() methods to the filters. + +Changed the Document's DocType storage so it's part of the ContentList now and +has a location that can be preserved. This helps with round tripping. The +getDocType() and setDocType() methods remain for convenience but just operate +based on searches through the ContentList. Adding logic to ensure the DOCTYPE +isn't added after the root, or vice-versa. + +The Attribute class now trims its value before attempted conversion to a +double, float, or boolean values. Also "1" and "0" are legal boolean values +following the lead of Schema. + +Added better support for loading from files whose names have special +characters like #. + +Added a protected SAXHandler.flushCharacters(String) method to allow +subclassers to have more control over strings. + + +BUG FIXES +--------- + +Fixed bug in AttributeList.clear() where the cleared list did not reset its +size to 0, causing NullPointerException when the list was reused. + +Fixed bug where serializing a content list using a filter. It wouldn't work +because FilterList wasn't serializable. It is now. + +Fixed bug in ContentList that could theoretically cause problems during +reverse iteration. + +Changed JDOMException.initCause() to return "this" instead of "cause" +since that's what Throwable says it should do. + +Fixed bug with elt.isAncestor() where it had been acting like "is descendant". + +Fixed bug in DOMOutputter where it could have problems outputting documents +with a DocType. + + +DEPRECATED METHODS +------------------ + +Deprecated the Document(List, DocType) constructor because it doesn't make +sense if the DocType should be part of the content list rather than separate. + +Deprecated the XMLOutputter.set*() methods that now reside in Format. + +Deprecated the XMLOutputter constructors that took format parameters. Use the +constructor that accepts a Format now instead. + +Deprecated the Attribute constants: + Attribute.CDATA_ATTRIBUTE + Attribute.ID_ATTRIBUTE + Attribute.IDREFS_ATTRIBUTE, etc. +Their new names are simpler: + Attribute.CDATA_TYPE + Attribute.ID_TYPE + Attribute.IDREFS_TYPE, etc. + +Renamed methods from the PI class to be more consistent and explanatory. +Deprecating: + getNames() + getValue() + setValue() + removeValue() +New names: + getPseudoAttributeNames() + getPseudoAttributeValue() + setPseudoAttribute() + removePseudoAttribute() + +Deprecated the methods setTextTrim(), setTextNormalize(), and +setTrimAllWhite(). Replaced them with setTextMode(Format.TextMode) and +getTextMode(). + +Changed the protected method SAXHandler.setAlternateRoot() to pushElement(). + + +REMOVED CLASSES +--------------- + +None. + + +REMOVED METHODS +--------------- + +Removed everything deprecated in b9. (JDOM deprecates for one beta cycle, +then removes.) + +Made the DOMOutputter output(Element, ...) and output(Attribute, ...) private. +They used to be protected. Skipped the deprecation step because you couldn't +actually extend them because of the NamespaceStack visibility, and we don't +usually deprecate protected methods. + + +SPECIAL NOTE +------------ + +Reduced the visibility on many items from protected to private. Anything +protected or public must be supported in the future, and we don't want to +promise that for anything unless it's absolutely necessary. If your JDOM +extension has problems with the reduced visibility that can't be overcome, +write to jdom-interest-AT-jdom.org with your issue. We'll crack open the +visibility as proven necessary. + + +* * * * * * Beta9 (tag: jdom_1_0_b9) from Beta8 * * * * * * + +NEW PACKAGES +------------ + +Added org.jdom.xpath package for XPath manipulations. + + +NEW CLASSES +----------- + +Added the XPath and JaxenXPath classes to the new org.jdom.xpath package. + +Added org.jdom.input.JDOMParseException, a subclass of JDOMException, to be +thrown by the builders to convey whatever information could be gathered from +the parser about the error. It has a getPartialDocument() that gives access +to whatever part of the input document that was successfully parsed before the +parser fired a SAXParseException. + +Added org.jdom.output.JDOMLocator, an implementation of org.xml.sax.Locator +which a ContentHandler could use to determine the document object on which an +error occurred. + + +NEW METHODS +----------- + +Added the the getResult()/setResult() methods to JDOMResult to support +transformations that return results other than a document. + +Added saxBuilder.setReuseParser(boolean) with a default of false. Turning it +on allows reuse and faster performance for parsers that support reuse. + +Added a ProcessingInstruction.setTarget(String newTarget) method. Once we +decided to allow elt.setName() we should allow pi.setTarget(). + +Added a SAXOutputter.getLocator() method to make the locator available outside +the ContentHandler. For example, this allows you to have ErrorHandlers not +implementing XMLFilters. + + +ENHANCEMENTS +------------ + +Fixed the TextBuffer performance problem that made performance terrible on +certain JVMs in beta8. + +Changed CDATA to extend Text so now you can look just for Text nodes +in content and don't need to differentiate CDATA sections if you don't +want to. This does require "instanceof CDATA" to come before "instanceof +Text" now. Watch out for that potential subtle bug. + +Changed the exception types that may be thrown from the SAXBuilder. The idea +is that SAXBuilder should throw an IOException for an I/O error, a +JDOMException for an XML problem, and that unexpected runtime exceptions +should not be hidden. Previously it could only throw a JDOMException, so this +will break existing code! Builders will now need to catch IOException. + +Changed the DOM adapter classes to throw either IOException, a JDOMException +wrapped around a parser-specific exception, or a subtype of RuntimeException. +Previously they might throw any Exception. + +Changing doctype.equals() to be == instead of comparing its elt name, system +id, and public id. Did this because someone may not care about the elt name +in comparing doc types, while someone else might care about the internal DTD +subset. This lets the default behavior be == and lets users write their own +equality check. + +Added a feature to prevent firing DTD events by setting the SAX core feature +"http://xml.org/sax/features/validation" to false. + +Removed the special namespace treatment of xml:space and xml:lang so they are +now treated as any other attributes in a namespace. + +Enhanced SAXOutputter to notify of entities through ContentHandler's +skippedEntity() and notify of some errors to the registered SAX ErrorHandler, +Added support to output Comments as Element children. Added support for CDATA +(i.e. distinguish CDATA from plain text and use of the start/endCDATA +callbacks of LexicalHandler). Removed support for CDATA as children of the +document node. + +Added CVS_ID and @version tags to source files that were missing them. + +Added JDOM_FEATURE constants to JDOMSource and JDOMResult which can be used +with TransformerFactory.getFeature() to determine if the transformer natively +supports JDOM. + +Moved the printing of the line seperator after the doctype up to +output(Document, Writer). This allows someone who doesn't want a newline +after the decl to kludge it away. + +Improved the Verifier's checking of Comment contents. Added logic to ensure +you can't have two default namespace declarations on the same element. + +Numerous performance improvements. + +Many Javadoc improvements. + +Numerous build script enhancements. + +Moved samples into the default package, building to build/samples. + + +BUG FIXES +--------- + +Fixed Document.clone() to set the new DocType. + +Fixed a bug where the JDOMException.printStackTrace(PrintWriter) method would +print some data to System.err instead of to the writer. + +Fixed bug in PI map parse logic which could be confused when there was a lot +of whitespace surrounding the = signs. + +Fixed bug where AttributeList.set() would not set parentage in one situation. + +Fixed bug where namespace prefixes were being lost on DOM builds. + +Fixed bug in PI.toString() which could cause funny output on PIs without data. + +Fixed a problem with the CDATA.clone() method that would result in CDATA +sections being cloned as Text objects rather than CDATA objects. + +Fixed the SAXOutputter so elementContent() fires a comment() event. + +Fixed bug where mapping a value to an attribute wouldn't happen if the +attribute was renamed. + +Fixed bug in XMLOutputter where \n was used instead of +currentFormat.lineSeparator at one location. + +Fixed a whitespace output bug where whitespace-only content would be treated +as empty. + +Fixed bug where the "xml" prefix wasn't available in Element's +getNamespace(String) call. + + +DEPRECATED METHODS +------------------ + +Deprecated the XMLOutputter.setIndent* methods except setIndent(String). + +Deprecated DOMBuilder(boolean validate), DOMBuilder(String adapter, boolean +validate), and DOMBuilder.setValidation(boolean validate) because validation +only matters when building from files, and those methods have already been +deprecated. + +Deprecated the DOMOutputter output methods that return a DOM Element or Attr, +because they aren't truly useful and the DOM contract is that every Attr and +Element (and every other Node too) always belongs to exactly one Document or +DocumentFragment object. + +Deprecated element.removeChildren() because it's a nearly useless method. +Call element.getChildren().clear() in the very rare case you want to remove +only the children but leave other content. + +Deprecated element.hasChildren() because it's not a performance optimization +anymore. This helps simplify the most complicated class around. + +Deprecated element.setChildren(List) since element.setContent(List) suffices. + +Deprecated xmlOutputter.outputString(String) since outputString(Text) handles +the Text nodes that now really reside within documents. + + +REMOVED CLASSES +--------------- + +Removed ProjectXDOMAdapter since the parser is no longer important. + + +REMOVED METHODS +--------------- + +Removed element.addContent(CDATA) and removeContent(CDATA) since +addContent(Text) and removeContent(Text) do the job now that CDATA extends +Text. + +Removed canAdd() and canRemove() from Filter. matches() is sufficient. + +Removed the methods deprecated in beta7. + + +SPECIAL NOTE +------------ + +Beginning with this release JDK 1.1 is no longer supported. You'll need JDK +1.2 or later. + + +* * * * * * Beta8 (tag: jdom_1_0_b8) from Beta7 (tag: jdom_1_0_b7) * * * * * * + +NEW CLASSES +----------- + +Added a Text class. This class is primarily for internal use to store String +data, so strings can now have parentage. A getText() will still return a +String. The Text nodes themselves can be retrieved through a getContent() +call. + +Added the public interface org.jdom.filter.Filter to support the "FilterList" +functionality. + +Added org.jdom.filter.ContentFilter, a standard filter for Content. And added +org.jdom.filter.ElementFilter, a standard filter for Element data. + +Added two non-public support classes to support the "FilterList" +functionality: ContentList and AttributeList. + + +NEW METHODS +----------- + +Added to Element and Document the method getContent(Filter) that takes a +Filter instance. + +Added to CDATA the methods getTextTrim(), getTextNormalize(), append(String), +append(CDATA), getParent(), getDocument(), and detach(). This brings CDATA +close in line with the Text class. They'll may become the same class with a +flag differentiator in the next beta. + +Added to Element the methods addContent(Text) and removeContent(Text). These +methods support the new Text class. + +Also added to Element the method removeAttribute(Attribute). This method was +simply overlooked before. + +Also added to Element the method getChildTextNormalize(). This method is +similar to getChildTextTrim(). + +Also added to Element two new styles of getAttributeValue() which let the +programmer specify default values if the attribute doesn't exist. + +Added to SAXBuilder the methods setFeature() and setProperty(). These methods +to allow programmers to customize the underlying parser. + +Added to SAXOutputter the new method setLexicalHandler(LexicalHandler). Also +added a new SAXOutputter constructor that takes a LexicalHandler as its last +argument. + +Added to ProcessingInstruction the method getNames(). This method returns the +pseudo-attribute names in the PI's data. + +Added to DocType the methods setInternalDTDSubset(String) and +getInternalDTDSubset(). These methods support new functionality where a +DocType can store and alter the internal DTD subset information. + +Also added to DocType the method setElementName(). + +Added a no-arg SAXOutputter constructor. + +Added to SAXOutputter the methods getContentHandler(), getErrorHandler(), +getDTDHandler(), getEntityResolver(), getLexicalHandler(), setDeclHandler(), +getDeclHandler(), setFeature(), setProperty(), getFeature(), and +getProperty(). + +Added to Attribute the methods getAttributeType() and setAttributeType(). +Also added various constructors that take an int type. These methods and +constructors support the new functionality where attributes can record their +type information. Note: this is something DOM can't do! + +Added to Document the method detachRootElement(). + +Added to XMLOutputter the methods outputString(List list), outputString(String +str), outputString(Text text), output(List list, OutputStream out), and +output(List list, Writer out). + +Added to EntityRef the constructor EntityRef(String name, String systemID). +This supports building an EntityRef without a public ID. + +Added to Verifier the methods checkSystemLiteral() and checkPublicID(). + + +NEW CONSTANTS +------------- + +Attribute has new constants for each attribute type: + UNDECLARED_ATTRIBUTE, CDATA_ATTRIBUTE, ID_ATTRIBUTE, IDREF_ATTRIBUTE, + IDREFS_ATTRIBUTE, ENTITY_ATTRIBUTE, ENTITIES_ATTRIBUTE, NMTOKEN_ATTRIBUTE, + NMTOKENS_ATTRIBUTE, NOTATION_ATTRIBUTE, and ENUMERATED_ATTRIBUTE. + + +NEW SIGNATURES +-------------- + +The XMLOutputter escape*() methods are now public. + +The Verifier checkXMLName() method is now public. + +Changed the protected "Element parent" variable for classes to be "Object +parent", with the object capable of serving double duty as either a Document +parent or Element parent. Saves noticeable memory. + +Changed the no-arg Document constructor to be public, along with Javadocs +explaining how the method is to be used. + + +REMOVED CLASSES +--------------- + +None. + + +REMOVED METHODS +--------------- + +Removed the methods deprecated in beta7. + + +DEPRECATED METHODS +------------------ + +Deprecated the DOMBuilder.build() methods that build from a File, URL, or +InputStream. This helps people understand those methods are for testing only. +DOMBuilder.build(org.w3c.dom.Document) and such are still undeprecated. + + +ENHANCEMENTS +------------ + +Added the long-awaited "FilterList" functionality! This improves the +reliability and performance of the lists returned by getContent() and +getChildren() calls. These lists are now fully live, they fully enforce +well-formedness constraints, and they don't require in-memory copying before +returning. A huge improvement! + +Integrated the Text class for wrapping strings behind the scenes and thus +allowing strings to have parentage. + +Added the ability for the DocType to have an internal DTD subset, and changed +the SAX and DOM builders and outputters to support this change. + +Added the ability for a Document to have a detached root to make elt.detach() +work easily. There will be an IllegalStateException thrown in case of read +from such a Document. + +Added support for "attribute types". Typing is now recorded within the +attribute object and fully managed during build and output. + +Rearchitected the internals of SAXBuilder and SAXHandler to be more extensible +via subclassing. Also exposed more of the internals of SAXHandler to make +subclassing easier. + +Made SAXOutputter much more robust, and made JDOMSource (used for +transformations) more robust along with it. + +Changed setContent(null) to now clear the content list and does not throw an +exception. Same for setChildren(null). + +Improved XMLOutputter to respect the xml:space attribute. + +Improved reporting behavior of build error messages. + +Improved how JDOMException reports on nested exceptions. + +Updated the Ant build system to version 1.4. + +Improved JDOM build versioning so we have versions like "1.0beta8-dev" for +work after Beta8, and "1.0beta8" will only be the actual Beta8 code. + +Changed the Javadocs to use CVS Revision and Date tags for @version. + +Many Javadoc clarifications. + +Improved the Verifier error message when adding a PI with an "xml" target, +since parsers and/or people have been trying to add it as a PI. + +Added verification of the system and public ID's in both DocType and +EntityRef, the root element name in DocType, and the entity name in EntityRef. + +Added ability for DocType and EntityRef to differentiate a missing ID from the +empty string ID. + +Changed the MANIFEST.MF to no longer list Xerces in the Class-Path entry, nor +to have JDOMAbout as its Main-Class. This helps applet deployment, but does +remove the ability to do the cool "java -jar jdom.jar". + +Added support for skipped entities in SAXHandler in the event that the parser +is not resolving external entities. + +Added well-formedness checking to ensure there are never duplicate attributes. + +Many, many performance optimizations throughout. + +Made Xerces 1.4.4 the default parser in "lib/xerces.jar". + + +BUG FIXES +--------- + +Fixed XMLOutputter to no longer add spurious newlines after closing element +tags. + +Fixed SAXBuilder to work better with XML filters. + +Fixed SAXHandler bug where attributes that had a namespace were being added to +the Document, but did not have the Namespace correctly reported. + +Fixed bug where a ProcessingInstruction or DocType removed from a Document did +not have its parentage set to null. + +Fixed bug where SAXBuilder would cache the class name even when using JAXP to +create the parser, causing problems for parsers without no-arg constructors. + +Fixed bug where Namespace collision checking could generate false positives. + +Fixed bug where a document containing a huge number of character entities +would cause JDOM builds to slow down exponentially. + +Fixed the many bugs caused by the old PartialList code, by replacing it with +FilterList code. + + +* * * * * * Beta7 (tag: jdom_1_0_b7) from Beta6 (tag: jdom_1_0_b6) * * * * * * + +NEW CLASSES +----------- + +Added JDOMSource and JDOMResult to the org.jdom.transform package. These +support XSLT transforms using the JAXP TrAX model. Added Crimson, JAXP, and +Xalan JARs to the lib directory to support the transform functionality. + +Added org.jdom.EntityRef to replace org.jdom.Entity. Changed methods taking +Entity to take EntityRef. + +Made org.jdom.input.SAXHandler a public class. It used to be package +protected. This is helpful to classes that want to build JDOM from a SAX +source, such as JDOMResult. + +Added org.jdom.input.JDOMFactory/DefaultJDOMFactory to support the builder +factory model. + +Added org.jdom.adapters.JAXPDOMAdapter to contain all the logic for +interacting with JAXP. Most people will never use this class. + +Added org.jdom.Text to the repository. It's not yet used. + + +NEW METHODS +----------- + +Added a new detach() method to each of the classes Attribute, Comment, +Element, EntityRef, and ProcessingInstruction. It removes the node from its +parent. + +Added setName(String) and setNamespace(Namespace) to Element and Attribute. + +Added elt.setAttribute() method, to replace elt.addAttribute(). It replaces +any existing attribute by the same name, instead of throwing an exception as +addAttribute() did. + +Added elt.getContent() and elt.setContent() methods, to replace +elt.getMixedContent() and elt.setMixedContent(). Did the same on Document. + +Added SAXBuilder.setExpandEntitities(boolean) method to indicate if entities +should be expanded, or if EntityRef objects should appear within the document. + +Added two new Document constructors to support constructing with a list of +content: + Document(List) + Document(List, DocType) + +Added elt.removeNamespaceDeclaration(Namespace). It removes a namespace +declaration, the counterpart to addNamespaceDeclaration(Namespace). + +Added a new constructor in IllegalAddException to account for a Namespace +illegally added: + IllegalAddException(Element base, Namespace added, String reason) + +Added getDocument() method to DocType. Added a protected setDocument() method +also. + +Added setFactory() method to SAXBuilder/DOMBuilder to support the factory +build model. + +Added elt.getTextNormalize() to return a normalized string (external +whitespace trimmed, internal whitespace reduced to a single space). The +getTextTrim() method now does a true trim. + +Added a SAXBuilder.setIgnoringElementContentWhitespace(boolean) method with +behavior that matches the method by the same name in JAXP's +DocumentBuilderFactory. Setting the value to true causes +ignorableWhitespace() to operate like a no-op. By default its value is false. + +Added getCause() to JDOMException, replacing getRootCause(). This new name +matches JDK 1.4. + +Added setOmitDeclaration on XMLOutputter, replacing the now-deprecated +setSuppressDeclaration(). + +Added elt.removeContent(CDATA) which was previously overlooked. + +Added protected methods in SAXBuilder to make it easier to extend: + protected XMLReader createParser() + protected SAXHandler createContentHandler() + protected void configureContentHandler(SAXHandler) + +Added getDocument() method to Attribute. + + +NEW SIGNATURES +-------------- + +DOMAdapter methods now may throw Exception instead of IOException. DOMBuilder +and DOMOutputter have the same API as always. + +Changed XMLOutputter's protected printXXX() methods to have a new signature +without the "int indentLevel" final parameter. Didn't bother with +deprecation. + +Changed XMLOutputter's printEntity() method to printEntityRef(). + +Made SAXBuilder's build(InputSource) method public. It used to be protected. + + +REMOVED CLASSES +--------------- + +Removed org.jdom.Entity; it's replaced by EntityRef. + + +REMOVED METHODS +--------------- + +Removed various methods that were previously deprecated in beta6: + Document.addContent(Element) + Namespace.getNamespace(String prefix, Element context) + CDATA.setText(String) + +Removed Document's protected rootElement variable. + + +DEPRECATED METHODS +------------------ + +Deprecated constructor Attribute(String name, String prefix, String uri, +String value). Its parameter order was non-standard and it was not a useful +method. + +Deprecated XMLOutputter's setIndentLevel() method. Having a global indent is +better done with a stacked FilterOutputStream. The method is now empty. + +Deprecated XMLOutputter's setPadText() method. It's not needed with the +current output mechanism. The method is now empty. + +Deprecated Element's getCopy(String) and getCopy(String, Namespace). These +can better be done now with a clone() and setName()/setNamespace(). + +Deprecated elt.addAttribute(). It's replaced by elt.setAttribute(). + +Deprecated getMixedContent() and setMixedContent() on Element and Document. +They're replaced by getContent() and setContent() versions. + +Deprecated getSerializedForm() methods on all objects, and moved the logic +into XMLOutputter. + +Deprecated the various xxxProcessingInstruction() methods in Document: + List getProcessingInstructions() + List getProcessingInstructions(String target) + ProcessingInstruction getProcessingInstruction(String target) + boolean removeProcessingInstruction(String target) + boolean removeProcessingInstructions(String target) + Document setProcessingInstructions(List pis) + +Deprecated the SAXHandler constructor SAXHandler(Document document) since now +the handler constructs the document itself. + +Deprecated elt.hasMixedContent() because it's of little use and its behavior +isn't well defined. + +Deprecated getRootCause() on JDOMException in favor of getCause(). This new +name matches JDK 1.4. + +Deprecated XMLOutputter's setSuppressDeclaration() in favor of +setOmitDeclaration() to better match setOmitEncoding(). + +Deprecated elt.addAttribute(String name, String prefix, String value). +Instead, setAttribute() should be used. + + +ENHANCEMENTS +------------ + +Clarified and improved many, many javadocs. + +Performance enhancement for files with namespaces. This improves build times +on one test from 13000ms to 580ms. + +Added support for the DOM DocumentType object when constructing documents +using DOMOutputter. + +Added a check that only one element is allowed in the document list as the +root. + +Added informational XML files in the jdom.jar META-INF directory storing +things like the version, credits, description, etc. These can be accessed +with a "java -jar jdom.jar" command which uses JDOM to read the info about +JDOM. + +Added JDOM version info to the MANIFEST.MF so servlets and such can depend on +it using http://java.sun.com/j2se/1.3/docs/guide/extensions/versioning.html + +Made elt.setMixedContent() check object types and parentage, and set +parentage. + +For the JDK 1.1 build, added a replace target so @throws is replaced by +@exception, which is the old JDK 1.1 javadoc keyword. + +Improved XMLOutputter internals so it no longer uses list.get(i) and instead +uses an Iterator. Should lighten the burden on outputting large documents. + +Added CVS Id variable to the top of each file for better tracking. + +Changed pi.getValue("nonexistant") to return null instead of "". Also made it +so that any parse error aborts and clears the parse results. + +Created a new implementations of clone() without any constructor calls. + +Revamped XMLOutputter's output logic to better match expectations. + +Changed XMLOutputter flushing logic so output() methods handle their own +flush() at the end of writing so user flush() calls should no longer be +necessary. + +Made elt.setMixedContent() and doc.setMixedContent() appear atomic, even in +case of failure. + +Optimized attr.getQualifiedName() implementation for speed. + +Added logic to setAttribute() to ensure well-formedness by verifying the +attribute namespace prefix doesn't collide with an existing prefix on that +element (either on the element's own ns, an additional ns, or another +attribute's ns). + +Added logic to addNamespaceDeclaration() to ensure the prefix doesn't collide +with an existing prefix on the element. + +Changed DocType.equals() to check for equivalency and not reference equality. +Now two DocTypes are equals() if their constituent strings are equals(). This +makes general sense because if you want to compare the doctypes of two docs +you want to do an equivalency check. + +Added a private CVS_ID variable to the core classes containing RCS variables. +This allows you to examine the compiled class to determine the source from +which it was compiled. See jdom-contrib's Ident.java. + +Performance optimization in setAttribute() so that removeAttribute() on a +pre-existing attribute is only called when necessary, as determined by an +earlier scan through the attributes. This was submitted by Phil Nelson who +says it gave an 8% time savings during a fresh build. + +Integrated the factory model for SAXBuilder. See the new classes +DefaultJDOMFactory and JDOMFactory. + +Changed Element.getTextTrim() behavior to truly be only a trim(). It used to +do normalization. + +Changed Document and Element internal LinkedList implementation to ArrayList. +This change of list gives us a remarkable reduction in memory sizes for all +large documents tested, and gives a speed boost too. + +Changed Element parentage so only one variable is used for the parent. It may +be of type Element or Document depending on where the elt is placed in the +tree. This saves one instance variable's worth of memory for each element in +the tree. + +Added a new line after the DocType always, for better formatting. + +Made the SAXHandler smart enough to ignore xmlns attributes. They shouldn't +appear when SAXHandler is used with SAXBuilder but sometimes appear with +driven by a different parser, such as with JDOMResult. + +Made note that elt.getAdditionalNamespaces() returns an unmodifiable list, and +made the implementation enforce the rule. This change allows +Namespace.equals() to be implemented to compare URIs instead of resorting to +==, and more importantly it avoids having XMLOutputter trigger a new List +object creation for every element with an empty additional namespace list +(which is 99.9% of elements). + +Refactored SAXBuilder to make it easier to extend. +* The parser is created in a separate createParser() method, and + configured in a separate configureParser() method. +* The content handler is created in a separate createContentHandler() + method, and configured in a separate configureContentHandler() method. + +Improved builder exception handling to if anything in the build process throws +a JDOMException, we no longer wrap it in another JDOMException; we just +rethrow as-is. + +Made XMLOutputter expose its NamespaceStack using an inner class, so +subclassers could have access to the stack. + + +BUG FIXES +--------- + +Fixed bug where Element.clone() didn't copy over PIs. + +Made DOMOutputter check if there was a pre-existing root element on a new +document, and if so call replaceChild() instead of appendChild(). This is +necessary for Xerces 1.3 where new documents are created with a default + element. + +Fixed a bug where Attr output(Attribute) wasn't using JAXP. + +Improved the logic by which ProcessingInstruction parses attribute-style +values. The old logic was confused by whitespace, nested quotes, etc. + +Added sanity check in DOMBuilder to ignores null NodeList and Node entries. +Per the DOM2 spec neither should ever be null, but that doesn't mean some DOM +implementations don't return null. + +Fixed bug in Namespace.getNamespace() where the lookup for a pre-existing +identical namespaces would fail even if there was a pre-existing identical +namespace. This caused new Namespaces to be created on all +Namespace.getNamespace() calls! + +Fixed bug where elt.clone() would concatenate adjacent strings. + +Fixed bug in elt.hasChildren() where the logic could be confused if there was +a subclass of Element in the tree. + +Fixed the Javadoc on Element.getAdditionalNamespaces() to say it returns an +empty list if empty. It used to say null. Empty is consistent with JDOM +elsewhere. + +Fixed bug where adding a null to a setMixedContent() method would cause an NPE +while constructing the error message. + +Fixed bug in SAXHandler where namespaces weren't being removed from the +available list, causing memory bloat. + +Fixed DOMBuilder so it works better on non-namespace-aware documents. Now if +getLocalName() returns null we look for a specific tagname/attname. + +Added ignorableWhitespace() method to SAXHandler to capture ignorable +whitespace. It can be turned off with +builder.setIgnoringElementContentWhitespace(true). + +Changed Namespace.equals() to check equivalency based only on URI. It used to +be both URI and prefix. This new behavior is more in line with standard XML. +It's unlikely but possible that existing code might break because of this, if +any code puts Namespace objects into a collection and doesn't expect +namespaces with different prefixes to be treated identically. No deprecation +is possible though. Also fixed behavior of Namespace.hashCode() to depend +solely on the URI. + +Fixed bug where DOMOutputter creates nodes with "" as their +node.getNamespaceURI() even if the node is not in a namespace. + +Changed attribute value escaping to not escape single-quotes because it's not +necessary as attribute values are always surrounded by double-quotes. + +Made sure XMLOutputter doesn't print the newline after the decl if the decl is +suppressed. + +Fixed SAXOutputter to declare namespaces using start/endPrefixMapping methods. +Also added optional ability for SAXOutputter to report namespace declarations +(the xmlns: attributes) if setReportNamespaceDeclarations() is true. + +Fixed performance bug where namespaces were continuously being added to the +availableNamespaces list unnecessarily, causing roughly as many entries to be +added as there were elements with namespaces. In simple testing, memory usage +for representing a namespace-intensive file went from 1.4 Megs to 460K. + +Fixed addFirst() and addLast() in PartialList to work correctly. + +Fixed a bug in PartialList where addAll() added *before* the last element +instead of after. + +Made PartialList's addAll() method work OK if the backing list is non-empty +while the PartialList is empty. + +Fixed build scripts to work OK on Windows with spaces in directory paths. + + +NEW ARCHIVES +------------ + +Added new *searchable* mailing list archives at +http://www.servlets.com/archive/servlet/SummarizeList?listName=jdom-interest + + +* * * * * * * * * * Beta6 from Beta5 * * * * * * * * * * + +NEW CLASSES +----------- + +Added new class org.jdom.input.BuilderErrorHandler as a default error handler +for JDOM builders. It ignores warnings but throws on errors and fatal errors. + +Added a Crimson adapter CrimsonDOMAdapter.java, to support the parser slated +to come with JAXP 1.1. + + +NEW METHODS +----------- + +Added parentage for Attribute, Comment, Element, Entity, and PI! They all now +have getParent() methods. All but Attribute have getDocument() methods also. +The addContent() and addAttribute() methods now check parentage and don't +allow an item to be added if it's already held by another. + +Added to Element the method Namespace getNamespace(String prefix). It returns +the Namespace in scope with the given prefix. It helps with attributes whose +values include namespaces, like . + +Added DOMBuilder.setValidation(boolean) to set the validate flag after +construction, to match SAXBuilder. + +Added DOMOutputter.output(Attribute) methods. + +Added XMLOutputter.setExpandEmptyElements() to choose between and +. + +Many new XMLOutputter methods for outputting document fragments. + +SAXBuilder now has a setXMLFilter() method to allow setting of XMLFilter +objects to operate during the build. + +Added to Element a hasChildren() method. + +Added various removeContent() methods on Element. They were deprecated and +scheduled for removal, but they're now being kept. + +Added various removeContent() methods on Document. These are brand new. + + +NEW SIGNATURES +-------------- + +Made clone() methods no longer final. + +Made toString() methods no longer final. + +Changed all outputter output() signatures to throw JDOMException in case of +problem. + +Changed DOMAdapter signature so getDocument(String filename, ...) is now +getDocument(File filename, ...) to match the standard builder model. I did +not do a deprecation because no one should be using this internal API +directly, and if they are, I want to hear from them. + + +REMOVED METHODS +--------------- + +Removed all methods marked deprecated in beta5. + + +DEPRECATED METHODS +------------------ + +Marked Namespace.getNamespace(String prefix, Element context) deprecated +because it had been replaced by the more elegant elt.getNamespace(String +prefix). + +Marked Document.addContent(Element) deprecated because there can be only one +element and it's properly set with setMixedContent(). + +Marked CDATA.setText() as deprecated. This is because CDATA doesn't have +parentage, and without parentage an object should be immutable. + + +ENHANCEMENTS +------------ + +Added JAXP 1.1 support to SAXBuilder, DOMBuilder, and DOMOutputter. The +default parser for all these is now the JAXP parser, with a fallback of Xerces +if JAXP isn't available. + +Added improved Verifier checks of well-formedness throughout all of JDOM. +Among the most likely to be noticed: + - Added Verifier detection of wrongly places "xmlns" attributes. + - Added check in Attribute that a namespace with "" prefix must be + NO_NAMESPACE. + - Added Verifier checks so CDATA text cannot contain ">>]" + +Upgraded provided parser to Xerces 1.2. + +Improved SAXBuilder and DOMBuilder to be *much* smarter about namespaces. +Most likely to be noticed: + - DOMBuilder now keeps xmlns namespaces declaration location, and it now + relies on the parser to handle namespaces (necessary for importing a + document that has nodes moved around). + +Made SAXBuilder and DOMBuilder much more specific on error reporting. + +Brought DOMOutputter up to DOM Level 2 compliance. + - Added logic to DOMOutputter to add xmlns attributes to the DOM tree + where appropriate. + +Added SAXOutputter to generate SAX events. + +Improved documentation on clone() methods. + +Changed XMLOutputter.escape*Entities() to protected from private to help +subclasses. + +Improved removeContent() to solve a Crimson performance problem regarding +duplicate string entries. + +Added logic to prevent an element from being added as a child or descendent of +itself. + +Optimized SAXBuilder list handling so retrievals and removes will most likely +hit on their first try instead of their last try. + +Added Namespace output to Element.toString() to help debugging element +namespace issues. + +Improved the Verifier.isXML*() methods to operate much faster. + +XMLOutputter now prints new lines after the declaration, even if newlines are +turned off for the rest of the document. + +Made PI's getSerializedForm() smarter about spacing between target and data. +Now if there is no data, there's no space added. + +Guarantee XMLOutputter prints a new line at the end of each document, for +better formatting, esp when printing to System.out. + +Put samples in the "samples" package. + + +BUG FIXES +--------- + +Fixed bug in XMLOutputter where "additional namespace" declarations would be +output even if they were already declared by an ancestor. + +Fixed bug where an element not in any namespace will still inherit the default +namespace from an ancestor. + +Added fix to recognize implicit "xml" namespace during +Namespace.getNamespace() call. + +Added fix so XMLOutputter no longer outputs XML_NAMESPACE. + +Fixed Element.getDocument() behavior to work reliably. + +Fixed Verifier to not see "xmlnsfoo" attributes as invalid. + +Fixed Verifier to allow attribute names xml:lang and xml:space as special +cases. + +Improved all adapters to throw exceptions on error instead of printing stack +traces. + +Fixed Element.clone() to be a true deep copy. + +Fixed bug in SAXBuilder that would throw an EmptyStackException if a PI +appeared after the root element. + +Fixed bug in doc.setMixedContent(List) so it now stores the new data +correctly. + +Made removeChildren() properly set parents to null, and to return true only if +children were actually deleted. + +Changed SAXBuilder's endPrefixMapping(String, String) to be +endPrefixMapping(String) as it should have been so we now get the callback and +can remove namespaces. + +Fixed PartialList.addAll() to behave as specified. + diff --git a/COMMITTERS.txt b/COMMITTERS.txt new file mode 100644 index 0000000..81a3bca --- /dev/null +++ b/COMMITTERS.txt @@ -0,0 +1,9 @@ +The following people are committers on the "jdom" repo. + +DO NOT WRITE THESE PEOPLE FOR TECH SUPPORT. THEY WILL NOT ANSWER. +WRITE THE jdom-interest MAILING LIST AT http://jdom.org. + +Jason Hunter is project maintainer. + +Rolf Lear is leading the JDOM 2.0 work. + diff --git a/FindBugsExcludeFilter.xml b/FindBugsExcludeFilter.xml new file mode 100644 index 0000000..b07ad86 --- /dev/null +++ b/FindBugsExcludeFilter.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/FindBugsIncludeFilter.xml b/FindBugsIncludeFilter.xml new file mode 100644 index 0000000..361a75a --- /dev/null +++ b/FindBugsIncludeFilter.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..2474698 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,54 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..48faade --- /dev/null +++ b/README.txt @@ -0,0 +1,122 @@ +This is a patched versions of JDOM project which is used in IntelliJ Platform. +It's based on JDOM 2.0.x version and patched to restore compatibility with JDOM 1.1. + +Introduction to the JDOM project +================================ + +Please see the JDOM web site at http://jdom.org/ +and GitHub repository at https://github.com/hunterhacker/jdom/ + +Quick-Start for JDOM +===================== +See the github wiki for a Primer on using JDOM: +https://github.com/hunterhacker/jdom/wiki/JDOM2-A-Primer + +Also see the web site http://jdom.org/downloads/docs.html. It has links to +numerous articles and books covering JDOM. + + +Installing the build tools +========================== + +The JDOM build system is based on Apache Ant. Ant is a little but very +handy tool that uses a build file written in XML (build.xml) as building +instructions. For more information refer to "http://ant.apache.org". + +The only thing that you have to make sure of is that the "JAVA_HOME" +environment property is set to match the top level directory containing the +JVM you want to use. For example: + +C:\> set JAVA_HOME=C:\jdk1.6 + +or on Mac: + +% setenv JAVA_HOME /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + (csh) +> JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home; export JAVA_HOME + (ksh, bash) + +or on Unix: + +% setenv JAVA_HOME /usr/local/java + (csh) +> JAVA_HOME=/usr/java; export JAVA_HOME + (ksh, bash) + +That's it! + + +Building instructions +===================== + +If you do not have the full source code it can be cloned from GitHub. The JDOM +project at https://github.com/hunterhacker/jdom has the instructions and source +URL to make the git clone easy. + +You will need to have Apache Ant 1.8.2 or later, and you will need Java JDK 1.6 +or later. + +Ok, let's build the code. First, make sure your current working directory is +where the build.xml file is located. Then run "ant". + +If everything is right and all the required packages are visible, this action +will generate a file called "jdom-2.x-20yy.mm.dd.HH.MM.zip" in the +"./build/package" directory. This is the same 'zip' file that is distributed +as the official JDOM distribution. + +The name of the zip file (and the jar names inside the zip) is controlled by +the two ant properties 'name' and 'version'. The package is called +"${name}-${version}.zip". The 'official' JDOM Build process is done by +creating a file 'build.properties' in the 'top' folder of the JDOM code, and +it contains the single line (or whatever the appropriate version is): + +version=2.0.0 + +If your favourite Java IDE happens to be Eclipse, you can run the 'eclipse' ant +target, and that will configure your Eclipse project to have all the right +'source' folders, and 'Referenced Libraries'. After running the 'ant eclipse' +target, you should refresh your Eclipse project, and you should have a project +with no errors or warnings. + + +Build targets +============= + +The build system is not only responsible for compiling JDOM into a jar file, +but is also responsible for creating the HTML documentation in the form of +javadocs. + +These are the meaningful targets for this build file: + + - package [default] -> generates ./build/package/jdom*.zip + - compile -> compiles the source code + - javadoc -> generates the API documentation in ./build/javadocs + - junit -> runs the JUnit tests + - coverage -> generates test coverage metrics + - eclipse -> generates an Eclipse project (source folders, jars, etc) + - clean -> restores the distribution to its original and clean state + - maven -> generates the package, and makes a 'bundle' for maven-central + +To learn the details of what each target does, read the build.xml file. It is +quite understandable. + + +Bug Reports +=========== + +Bug reports go to the jdom-interest list at jdom.org. But *BEFORE YOU POST* +make sure you've tested against the LATEST code available from GitHub (or the +daily snapshot). Odds are good your bug has already been fixed. If it hasn't +been fixed in the latest version, then when posting *BE SURE TO SAY* which +code version you tested against. For example, "GitHub from October 3rd". Also +be sure to include enough information to reproduce the bug and full exception +stack traces. You might also want to read the FAQ at http://jdom.org to find +out if your problem is not really a bug and just a common misunderstanding +about how XML or JDOM works. + + +Searching for Information +========================= + +The JDOM mailing lists are archived and easily searched at +http://jdom.markmail.org. diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..b7b048d --- /dev/null +++ b/TODO.txt @@ -0,0 +1,180 @@ +Items that need to be done: + +--- ITEMS REMAINING BEFORE 1.1 --- + +None! + +--- ITEMS TO CONSIDER FOR 1.2 --- + +* Integrate the contributed StAXBuilder. + +* Rusty's "base uri" support for Element and the rest. + +* Investigate a way to do in-memory validation. First step is probably + to get an in-memory representation of a DTD as per + http://xmlhack.com/read.php?item=626 + http://www.wutka.com/dtdparser.html + http://lists.denveronline.net/lists/jdom-interest/2000-July/001431.html + http://lists.denveronline.net/lists/jdom-interest/2001-February/004661.html + Maybe new DTDValidator(dtd).validate(doc); + Then later new SchemaValidator(schema).validate(doc); + Could instead do doc.validate(dtd/schema) but then we'd have to dynamically + switch between recognizing DTDs and the various schemas. + The method would probably either throw InvalidDocumentException or might + take an ErrorHandler-style interface implementation if there are non-fatal + errors possible. + It'd also be possible to have a programmatic verifier, that determined for + example if an orderid="100" entry was valid against a database entry. + http://dcb.sun.com/practices/devnotebook/xml_msv.jsp + http://www.sun.com/software/xml/developers/multischema/ + +* Create an HTMLOutputter to handle the HTML specific aspects (closing tags, + escaped characters like é, etc). + +--- FUTURE IDEAS --- + +* Utility methods for comparing nodes by content instead of reference. + Hopefully base this on whatever standard emerges in this area. + +* Note in the docs where necessary our multithreading policy. + +* Create a JDOM logo. + +* Look at http://www.sosnoski.com/opensrc/xmls/format.html. + +* Look at interfaces for the core classes, Element with ConcreteElement being + our code. Base on the factory model. Allow no access between objects + except using the public API, to avoid the import node problem. Do the big + switchover using javax.xml.jdom as interfaces and default impl, use org.jdom + for the concretes. May not need to break existing code (sick and wrong). + - read-only? Experimentation happening in jdom-javax module. + +* Ensure JDOM is appropriately tweaked for subclassing, per the threads + started by Joe Bowbeer. + http://www.servlets.com/archive/servlet/ReadMsg?msgId=7601 begins it + +* Ensure JDOM is flawless regarding clone semantics, per more threads by + Joe Bowbeer. + http://www.servlets.com/archive/servlet/ReadMsg?msgId=7602 begins it + +* Joe summarizes his issues at + http://www.servlets.com/archive/servlet/ReadMsg?msgId=7697 + +* Add in attribute type support to DOM to match what's in SAX. + +* Look into implementing an id() method now that we have attribute types. + +* Look into how the factory builder model could support giving the factory + extra knowledge about the context (line number, element stack, etc), and + allow it to report errors or to return a code indicating the element should + be ignored. + (Laurent Bihanic wrote JH a private email about this on Dec 28 2001.) + +* Write a "GNU JAXP (i.e. AElfred) DOM adapter" (elharo looking into this). + +* Create "build dist" for distribution + Use fixcrlf in dist (instead of package as currently done) + Probably include source with jdom.jar built + +* Populate jdom-test. Hong Zhang once volunteered to + help with the J2EE CTS. + +* Add setIgnoringAllWhitespace(boolean) method. + +* Consider a listener interface so you could listen to doc changes. + (Probably after 1.1 honestly; this can be done through manual subclasses + already.) Some pertinent messages on this topic: + http://lists.denveronline.net/lists/jdom-interest/2000-July/001586.html + http://lists.denveronline.net/lists/jdom-interest/2000-July/001587.html + http://lists.denveronline.net/lists/jdom-interest/2000-July/001600.html + +* Consider a "locator" ability for nodes to remember the line number on which + they were declared, to help debug semantic errors. + http://lists.denveronline.net/lists/jdom-interest/2000-October/003422.html + +* Consider an XMLOutputter flag or feature to convert characters with well + known named character entities to their named char entity form instead of + numeric. + +* Determine if DOMBuilder and DOMOutputter should transparently support DOM1. + +* Create a builder based on Xerces' XNI, which will be more featureful and + probably faster than the one based on SAX. + See http://lists.denveronline.net/lists/jdom-interest/2001-July/007362.html + Some existing SAX limitations which hurt round-tripping: + * Can't tell if attribute values are included from the DTD, because SAX + doesn't tell if attributes are standalone/implicit + (See http://www.saxproject.org/apidoc/org/xml/sax/ext/Attributes2.html) + (Thought: could use a bit in the type value to save memory) + * Can't get access to retain the internal dtd subset unless entity + expansion is off + * Can't get access to whitespace outside the root element. + +* Write a guide for contributors. Short summary: + Follow Sun's coding guidelines, use 4-space (no tab) indents, no lines + longer than 80 characters + +* Consider a builder for a read-only document. It could "intern" objects to + reduce memory consumption. In fact, interning may be good for String + objects regardless. + +* Consider having the license be clear org.jdom is a protected namespace. + +* Think about the idea of using more inheritance in JDOM to allow + lightweight but not XML 1.0 complete implementations. For example Element + could have a superclass "CommonXMLElement" that supported only what Common + XML requires. Builders could build such elements to be faster and lighter + than full elements -- perfect for things like reading config files. Lots + of difficulties with this design though. + +* Create a Verifier lookup table as an int[256] growable to int[64K] where + bits in the returned value indicate that char's ability to be used for a + task. So "lookup[(int)'x'] & LETTER_MASK" tells us if it's a letter + or not. + +* Consider an HTMLBuilder that reads not-necessarily-well-formed HTML and + produces a JDOM Document. The approach I'd suggest is to build on top of + JTidy first. That gives a working implementation fast, at the cost of a + 157K Tidy.jar in the distribution. After that, perhaps someone would lead + an effort to change the JTidy code to build a JDOM Document directly, + instead of making a DOM Document or XML stream first. That would be a lot + faster, use less memory, and make our dist smaller. See + http://www.sourceforge.net/projects/jtidy for Tidy. + See post by Jacob.Robertson@argushealth.com on 2/13/2002. + +* Look at a (contrib?) outputter option using SAX filters per + http://lists.denveronline.net/lists/jdom-interest/2000-October/003303.html + http://lists.denveronline.net/lists/jdom-interest/2000-October/003304.html + http://lists.denveronline.net/lists/jdom-interest/2000-October/003318.html + http://lists.denveronline.net/lists/jdom-interest/2000-October/003535.html + +* Look at event-based parsing as per the following thread: + http://lists.denveronline.net/lists/jdom-interest/2000-November/003613.html + and replies. + Also see posts with the subject "streamdom". + +* Considering that local vars are considerably faster that instance vars, test + if using local vars can speed building. + +* Consider using a List of instance data so elements only use what they really + need (saving attrib list, namespace list) + +* Investigate doc.getDescription() to let people add doc descriptions. It's + an idea from IBM's parser suggested by andyk. + +* Work on creating a deferred builder that parses only what's necessary to + satisfy the programmer's requests. See Ayal Spitz' post at + http://lists.denveronline.net/lists/jdom-interest/2001-April/005685.html + +* Change the various setAttributeValue() methods in Element and + Attribute to check the attribute type and normalize the string + according to the attribute type. i.e. normalize the white space if + the attribute has any type other than CDATA or UNDECLARED. + +* Give attributes the "specified" flag like in DOM. This probably isn't + receivable from SAXBuilder, but it would be from DOMBuilder and other + builders. Then give XMLOutputter the ability to avoid outputting + "unspecified" attributes. + +* Should there be XPath support within Element, Document, etc? + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..3106678 --- /dev/null +++ b/build.gradle @@ -0,0 +1,7 @@ +allprojects { + apply plugin: 'java' + + repositories { + mavenCentral() + } +} diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..1a7982c --- /dev/null +++ b/build.xml @@ -0,0 +1,561 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/.gitignore b/contrib/.gitignore new file mode 100644 index 0000000..cb9f69b --- /dev/null +++ b/contrib/.gitignore @@ -0,0 +1,2 @@ +/build +/out \ No newline at end of file diff --git a/contrib/README.txt b/contrib/README.txt new file mode 100644 index 0000000..cb1ca49 --- /dev/null +++ b/contrib/README.txt @@ -0,0 +1,21 @@ +Introduction +============ + +jdom-contrib is a place for projects that increase the value of JDOM but +aren't (at least yet) in the core distribution. Contributed code is +placed under the org.jdom.contrib package hierarchy. + +Currently we have org.jdom.contrib packages for beans, helpers, ids, input, +output, and schema. "beans" holds JDOMBean and can hold related bean +work. "helpers" holds certain helper functions. "ids" demonstrates how to +use the attribute type support provided by JDOM to create JDOM documents that +allow looking up elements using the value of their ID attribute. "input" and +"output" hold builders and outputters. "schema" has code for in-memory +schema validation. + +Some code from contrib (or the equivalent functionality) has been migrated in +to the core code, The code has been left in contrib and been marked as +'deprecated', as example code of what can be done. + +If you have an interesting contribution, or just ideas that someone else might +pick up on, post to jdom-interest. diff --git a/contrib/build.gradle b/contrib/build.gradle new file mode 100644 index 0000000..f09b0ba --- /dev/null +++ b/contrib/build.gradle @@ -0,0 +1,16 @@ +dependencies { + compile project(':core') + compile 'junit:junit:4.12' + compile 'isorelax:isorelax:20030108' + compile 'xerces:xercesImpl:2.11.0' + compile 'xalan:xalan:2.7.2' +} + +sourceSets { + main { + java { + srcDir 'src/java' + } + } +} + diff --git a/contrib/samples/JTreeOutputterDemo.java b/contrib/samples/JTreeOutputterDemo.java new file mode 100644 index 0000000..011562f --- /dev/null +++ b/contrib/samples/JTreeOutputterDemo.java @@ -0,0 +1,234 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. All rights reserved. + + Redistribution and use in source and binary forms, with or without modifica- + tion, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions, the disclaimer that follows these conditions, + and/or other materials provided with the distribution. + + 3. The names "JDOM" and "Java Document Object Model" must not be used to + endorse or promote products derived from this software without prior + written permission. For written permission, please contact + license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor may + "JDOM" appear in their name, without prior written permission from the + JDOM Project Management (pm@jdom.org). + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + JDOM PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + This software consists of voluntary contributions made by many individuals + on behalf of the Java Document Object Model Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the JDOM + Project, please see . + + */ + +import java.awt.*; +import java.awt.event.*; +import java.net.URL; +import javax.swing.*; +import javax.swing.tree.*; + +import org.jdom.Document; +import org.jdom.input.SAXBuilder; +import org.jdom.contrib.output.JTreeOutputter; + +/** + *

JTreeOutputterDemo demonstrates how to + * build a JTree (in Swing) from a JDOM {@link Document}. + *

+ * + * @author Jon Baer + * @author Brett McLaughlin + * @version 1.0 + */ +@SuppressWarnings("javadoc") +public class JTreeOutputterDemo implements ActionListener { + + public JFrame frame; + public Document doc; + public DefaultMutableTreeNode root; + public JTreeOutputter outputter; + public JTree tree; + public JScrollPane scrollPane; + public SAXBuilder saxBuilder; + public JMenuItem openFile, openURL, openSQL, exitMenu; + public JButton openButton, reloadButton, exitButton, aboutButton; + + public static void main(String[] args) { + new JTreeOutputterDemo(); + } + + public JTreeOutputterDemo() { + + frame = new JFrame(" JDOM Viewer 1.0"); + JMenuBar menuBar = new JMenuBar(); + JMenu menu = new JMenu("File"); + openFile = new JMenuItem("Open XML File"); + openFile.addActionListener(this); + openURL = new JMenuItem("Open URL Stream"); + openURL.addActionListener(this); + openSQL = new JMenuItem("Query Database"); + openSQL.addActionListener(this); + exitMenu = new JMenuItem("Exit"); + exitMenu.addActionListener(this); + menu.add(openFile); + menu.add(openURL); + menu.add(new JSeparator()); + menu.add(openSQL); + menu.add(new JSeparator()); + menu.add(exitMenu); + menuBar.add(menu); + frame.setJMenuBar(menuBar); + + openButton = new JButton("Open"); + openButton.addActionListener(this); + reloadButton = new JButton("Reload"); + reloadButton.addActionListener(this); + exitButton = new JButton("Exit"); + exitButton.addActionListener(this); + aboutButton = new JButton("About"); + aboutButton.addActionListener(this); + JPanel buttonPanel = new JPanel(); + buttonPanel.add(openButton); + buttonPanel.add(reloadButton); + buttonPanel.add(exitButton); + buttonPanel.add(aboutButton); + + root = new DefaultMutableTreeNode("JDOM"); + + outputter = new JTreeOutputter(true); + + tree = new JTree(root); + + saxBuilder = new SAXBuilder(); + + scrollPane = new JScrollPane(); + scrollPane.getViewport().add(tree); + + frame.setSize(400,400); + frame.getContentPane().setLayout(new BorderLayout()); + frame.getContentPane().add("Center", scrollPane); + frame.getContentPane().add("South", buttonPanel); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent evt) { + System.exit(0); + } + }); + + frame.setVisible(true); + + } + + @Override + public void actionPerformed(ActionEvent e) { + // Open File + if (e.getSource() == openButton || e.getSource() == openFile) { + doFile(); + } + // Open URL + if (e.getSource() == openURL) { + doURL(); + } + // Query Database + if (e.getSource() == openSQL) { + doSQL(); + } + // Exit + if (e.getSource() == exitButton || e.getSource() == exitMenu) { + System.exit(0); + } + } + + public void doFile() { + JFileChooser fc = new JFileChooser(); + fc.setDialogTitle("Select an XML File"); + int returnVal = fc.showDialog(frame, "Load XML"); + if (returnVal == 0) { + try { + doc = saxBuilder.build(fc.getSelectedFile()); + } catch (Exception e) {e.printStackTrace();} + outputter.output(doc, root); + } + } + + public void doURL() { + URLDialog urlDialog = new URLDialog(frame); + if (urlDialog.getURL() != null) { + try { + doc = saxBuilder.build(new URL(urlDialog.getURL())); + } catch (Exception e) { + e.printStackTrace(); + } + outputter.output(doc, root); + } + } + + public void doSQL() { + // do nothing + } + +} + +class URLDialog extends JDialog implements ActionListener { + + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + public String url; + public JTextField urlField; + public JButton okButton, cancelButton; + + public URLDialog(Frame frame) { + super(frame, "Enter A URL", true); + urlField = new JTextField("http://"); + JPanel buttonPanel = new JPanel(); + okButton = new JButton("OK"); + okButton.addActionListener(this); + cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(this); + buttonPanel.add(okButton); + buttonPanel.add(cancelButton); + getContentPane().setLayout(new BorderLayout()); + getContentPane().add("North", urlField); + getContentPane().add("South", buttonPanel); + setSize(400, 150); + setVisible(true); + } + + public String getURL() { + return this.url; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == okButton) { + this.url = urlField.getText(); setVisible(false); + } + if (e.getSource() == cancelButton) { + setVisible(false); + } + } + +} diff --git a/contrib/samples/LineNumberSAXBuilderDemo.java b/contrib/samples/LineNumberSAXBuilderDemo.java new file mode 100644 index 0000000..04894ad --- /dev/null +++ b/contrib/samples/LineNumberSAXBuilderDemo.java @@ -0,0 +1,77 @@ +/*-- + + Copyright 2004 Jason Hunter. All rights reserved. + + Redistribution and use in source and binary forms, with or without modifica- + tion, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions, the disclaimer that follows these conditions, + and/or other materials provided with the distribution. + + 3. The names "JDOM" and "Java Document Object Model" must not be used to + endorse or promote products derived from this software without prior + written permission. For written permission, please contact + license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor may + "JDOM" appear in their name, without prior written permission from the + JDOM Project Management (pm@jdom.org). + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + JDOM PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + This software consists of voluntary contributions made by many individuals + on behalf of the Java Document Object Model Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the JDOM + Project, please see . + + */ + +import java.io.StringReader; +import java.util.Iterator; + +import org.jdom.Document; +import org.jdom.input.SAXBuilder; +import org.jdom.filter2.*; + +import org.jdom.contrib.input.*; + +/** + * @author Per Norrman + * + */ +@SuppressWarnings("javadoc") +public class LineNumberSAXBuilderDemo +{ + + public static void main(String[] args) throws Exception { + SAXBuilder builder = new SAXBuilder(); + builder.setSAXHandlerFactory(LineNumberSAXHandler.SAXFACTORY); + Document doc = builder.build(new StringReader(xml)); + + for (Iterator iter = doc.getDescendants(Filters.fclass(LineNumberElement.class)); + iter.hasNext(); ) { + LineNumberElement e = iter.next(); + System.out.println( + e.getName() + ": lines " + e.getStartLine() + " to " + e.getEndLine()); + } + + } + + private static String xml = + "\n\n\n\n\n\n\n\n"; + +} diff --git a/contrib/samples/ResultSetBuilderDemo.java b/contrib/samples/ResultSetBuilderDemo.java new file mode 100644 index 0000000..9a85792 --- /dev/null +++ b/contrib/samples/ResultSetBuilderDemo.java @@ -0,0 +1,108 @@ +/*-- + + Copyright 2000 Brett McLaughlin & Jason Hunter. All rights reserved. + + Redistribution and use in source and binary forms, with or without modifica- + tion, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions, the disclaimer that follows these conditions, + and/or other materials provided with the distribution. + + 3. The names "JDOM" and "Java Document Object Model" must not be used to + endorse or promote products derived from this software without prior + written permission. For written permission, please contact + license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor may + "JDOM" appear in their name, without prior written permission from the + JDOM Project Management (pm@jdom.org). + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + JDOM PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + This software consists of voluntary contributions made by many individuals + on behalf of the Java Document Object Model Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the JDOM + Project, please see . + + */ + +import java.sql.*; + +import org.jdom.*; +import org.jdom.output.*; + +import org.jdom.contrib.input.ResultSetBuilder; + +@SuppressWarnings("javadoc") +public class ResultSetBuilderDemo { + + // SQL tables copied from the Servlets.com ISP listing application + + static final String PREP = + "create table rsbd ( " + + "id int PRIMARY KEY, " + + "name varchar(255) NOT NULL, " + + "home_url varchar(255) NULL, " + + "contact_email varchar(255) NULL, " + + "contact_phone varchar(255) NULL, " + + "location varchar(255) NULL, " + + "comments long varchar NULL, " + + "free_hosting boolean NULL, " + + "state_flag tinyint NOT NULL, " + // submitted, rejected, live, dead + "submitter_email varchar(255) NULL, " + // not displayed + "created_time timestamp NOT NULL, " + + "modified_time timestamp NOT NULL " + + ")"; + + static final String FILL = + "insert into rsbd (id, name, home_url, contact_email, " + + "contact_phone, comments, free_hosting, state_flag, created_time, " + + "modified_time) values (2, 'sphere', null, 'info@sphere', " + + "'1234', 'cool', true, 10, " + + "{ts '1999-02-09 20:11:11.123455'}, " + + "{ts '1999-03-21 22:11:11.123455'})"; + + public static void main(String[] args) throws Exception { + // Tested against Cloudscape database that comes with the J2EE ref impl + Class.forName("COM.cloudscape.core.JDBCDriver"); + Connection con = + DriverManager.getConnection("jdbc:cloudscape:rsbd;create=true"); + + // Create and fill commands, needed only on the first run + Statement prep = con.createStatement(); + prep.executeUpdate(PREP); + + Statement fill = con.createStatement(); + fill.executeUpdate(FILL); + + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery( + "select id, name, home_url || contact_phone from rsbd"); + ResultSetBuilder builder = new ResultSetBuilder(rs); + builder.setAsElement(3, "num3"); + //builder.setNamespace(ns); + //builder.setAsElement("id", "newid"); + //builder.setAsElement("home_url", "newhome_url"); + //builder.setAsElement(4, "some4"); + //builder.setAsAttribute(4, "some4"); + //builder.setAsAttribute("state_flag"); + builder.setAsAttribute("created_time", "ctime"); + Document doc = builder.build(); + XMLOutputter outputter = new XMLOutputter(); + outputter.output(doc, System.out); + } +} diff --git a/contrib/samples/sxql.java b/contrib/samples/sxql.java new file mode 100644 index 0000000..97374bf --- /dev/null +++ b/contrib/samples/sxql.java @@ -0,0 +1,280 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +import java.sql.*; +import java.io.*; +import java.util.*; + +import org.jdom.*; +import org.jdom.output.*; +import org.jdom.contrib.input.ResultSetBuilder; + +/** + * A simple sample harness for JDOM ResultSetBuilder + * + * @author R.Sena (raff@aromatic.org) + */ +@SuppressWarnings("javadoc") +public class sxql { + + /** + * Return a connection (i.e. from a connection pool) + */ + public static Connection getConnection(String driver, String jdbcURL, + String user, String pass) + throws Exception { + Class.forName(driver); + return DriverManager.getConnection(jdbcURL, user, pass); + } + + /** + * Execute a SQL query and return the result as XML + * (as a String. But this can be changed to return a DOM/SAX/JDOM tree, + * to be used, for example, as input to an XSLT processor) + */ + public static String query(Connection con, String query, + String root, String row, + String ns, int maxRows, + Vector attributes, Vector elements) + throws Exception { + // Execute SQL Query + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery(query); + + // Create a ResultSetBuilder + ResultSetBuilder builder = new ResultSetBuilder(rs); + + // Configure some parameters... + + if (root != null) { + builder.setRootName(root); + } + + if (row != null) { + builder.setRowName(row); + } + + if (ns != null) { + String namespace = null; + String url = null; + int sep = ns.indexOf("/"); + + if (sep > 0) { + namespace = ns.substring(0, sep); + url = ns.substring(sep+1); + builder.setNamespace(Namespace.getNamespace(namespace, url)); + } + } + + if (maxRows > 0) { + builder.setMaxRows(maxRows); + } + + for (int i=0; i < attributes.size(); i++) { + String colName = attributes.get(i); + String attrName = null; + + if (colName.indexOf("/") >= 0) { + String col = colName; + int sep = col.indexOf("/"); + colName = col.substring(0, sep); + attrName = col.substring(sep+1); + } + + try { // If it looks like an integer, is the column number + int colNum = Integer.parseInt(colName); + + if (attrName == null) { + builder.setAsAttribute(colNum); // attrName = column Name + } + else { + builder.setAsAttribute(colNum, attrName); + } + } + catch (NumberFormatException e) { + // Otherwise it's the column name + if (attrName == null) { + builder.setAsAttribute(colName); // attrName = column Name + } + else { + builder.setAsAttribute(colName, attrName); + } + } + } + + // Rename element + for (int i=0; i < elements.size(); i++) { + String colName = elements.get(i); + String elemName = null; + + if (colName.indexOf("/") >= 0) { + String col = colName; + int sep = col.indexOf("/"); + colName = col.substring(0, sep); + elemName = col.substring(sep+1); + } + + try { // If it looks like an integer, is the column number + int colNum = Integer.parseInt(colName); + + if (elemName != null) { // It must have an element name + builder.setAsElement(colNum, elemName); + } + } + catch (NumberFormatException e) { + // Otherwise it's the column name + if (elemName != null) { // It must have an element name + builder.setAsElement(colName, elemName); + } + } + } + + // Build a JDOM tree + Document doc = builder.build(); + + // Convert the result to XML (as String) + XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); + ByteArrayOutputStream output = new ByteArrayOutputStream(); + outputter.output(doc, output); + return output.toString(); + } + + /** + * Usage + */ + public static void usage() { + System.err.println( +"usage: sxql [--root=root-element] [--row=row-element]"); + System.err.println( +" [--namespace=namespace/url] [--maxrows=max-rows]"); + System.err.println( +" [--attribute=column/attr] [--element=column/element]"); + System.err.println( +" driver url user pass query"); + System.err.println( +"where:"); + System.err.println( +" --root: set root element name (root-element)"); + System.err.println( +" --row: set row element name (root-element)"); + System.err.println( +" --namespace: set namespace (namespace, url)"); + System.err.println( +" --maxrows: set maximum number of rows (max-rows)"); + System.err.println( +" --attribute: column as attribute (column name/number, attribute-name)"); + System.err.println( +" --element: rename column (column name/number, element-name)"); + System.err.println( +" driver: driver class name"); + System.err.println( +" url: JDBC url"); + System.err.println( +" user: database user"); + System.err.println( +" pass: database password"); + System.err.println( +" query: SQL query"); + } + + /** + * Main entry point + */ + public static void main(String [] args) throws Exception { + String root = null; + String row = null; + String ns = null; + int maxRows = 0; + Vector attributes = new Vector(); + Vector elements = new Vector(); + + int i; + + // Read configuration parameters + + for (i=0; i < args.length; i++) { + if (args[i].startsWith("--")) { + if (args[i].startsWith("--attribute=")) + attributes.add(args[i].substring(12)); + else + if (args[i].startsWith("--element=")) + elements.add(args[i].substring(10)); + else + if (args[i].startsWith("--root=")) + root = args[i].substring(7); + else + if (args[i].startsWith("--row=")) + row = args[i].substring(6); + else + if (args[i].startsWith("--namespace=")) + ns = args[i].substring(12); + else + if (args[i].startsWith("--maxrows=")) + maxRows = Integer.parseInt(args[i].substring(10)); + } + else { + break; + } + } + + if (args.length - i != 5) { + usage(); + } + else { + System.out.println( + query(getConnection(args[i+0], args[i+1], args[i+2], args[i+3]), + args[i+4], root, row, ns, maxRows, attributes, elements)); + } + } +} diff --git a/contrib/src/java/org/jdom/contrib/android/TranslateTests.java b/contrib/src/java/org/jdom/contrib/android/TranslateTests.java new file mode 100644 index 0000000..b7e67c3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/android/TranslateTests.java @@ -0,0 +1,354 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.android; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Set; +import java.util.TreeSet; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + + +/** + * This class is a translation layer that translates JUnit4 tests to + * JUnit3 tests suitable for running with the Android Test harness. + *

+ * This class is a small part of an automated process described in the + * wiki page https://github.com/hunterhacker/jdom/wiki/JDOM2-and-Android + * + * @author Rolf Lear + * + */ + +@SuppressWarnings("javadoc") +public class TranslateTests { + + private static final String[] skipclasses = new String[] {".*StAX.*"}; + + private static final String[] skipmethods = new String[] { + ".*HighSurrogateAttPair.*", "bulkIntern", "testBuildString"}; + + private static final Pattern pat = Pattern.compile("^(.+/(\\w+))\\.class$"); + + private static String potentialTest(String zename) { + final Matcher mat = pat.matcher(zename); + if (mat.matches()) { + final String cname = mat.group(2); + if (cname.startsWith("Test") || cname.endsWith("Test")) { + for (String cm : skipclasses) { + if (Pattern.matches(cm, cname)) { + return null; + } + } + final String cp = mat.group(1); + return cp.replace('/', '.'); + } + } + return null; + } + + /** + * Convert the Jar file to a set of new JUnit3 tests. + * @param args The two args are expected to be a Jar file (unit tests), and an output directory + * @throws IOException + */ + public static void main(String[] args) throws IOException { + if (args.length != 2) { + throw new IllegalArgumentException("Usage: Jar SrcOutDir"); + } + final String jarname = args[0]; + + final File srcoutdir = new File(args[1]); + if (!srcoutdir.exists()) { + srcoutdir.mkdirs(); + } + if (!srcoutdir.isDirectory()) { + throw new IllegalArgumentException("Could not create/use SrcOutput directory: " + srcoutdir); + } + + final ArrayList classes = new ArrayList(); + + final ZipFile zfile = new ZipFile(jarname); + try { + final Enumeration e = zfile.entries(); + while (e.hasMoreElements()) { + final ZipEntry ze = e.nextElement(); + if (ze.isDirectory()) { + continue; + } + final String zename = ze.getName(); + if (zename.endsWith(".class")) { + final String classname = potentialTest(zename); + if (classname != null) { + TranslateTests tt = new TranslateTests(srcoutdir, classname); + classes.add(tt.translate()); + } + } + } + } finally { + zfile.close(); + } + + } + + private final Class tclass; + private final File outf; + + public TranslateTests(File outdir, String zename) { + System.out.println("Contemplating " + zename); + try { + tclass = Class.forName(zename); + } catch (Exception e) { + throw new IllegalStateException("Unable to load class " + zename, e); + } + final String path = zename.replace('.', '/') + "TT.java"; + outf = new File(outdir,path); + } + +/* + + +package com.example.android.skeletonapp.test; + +import org.jdom.test.cases.TestElement; + +import android.test.AndroidTestCase; + +@SuppressWarnings("javadoc") +public class JDOMMainTest extends AndroidTestCase { + + + public void testElement() { + assertTrue(Integer.valueOf(1) > 0); + TestElement te = new TestElement(); + te.testCoalesceTextNested(); + } +} + + + */ + + private String translate() throws IOException { + final String sname = tclass.getSimpleName(); + final StringBuilder sb = new StringBuilder(); + final String ret = tclass.getPackage().getName() + "." + sname + "TT"; + + sb.append("package ").append(tclass.getPackage().getName()).append(";\n"); + sb.append("@SuppressWarnings(\"javadoc\")\n"); + sb.append("public class ").append(sname).append("TT extends android.test.AndroidTestCase {\n"); + + if (tclass.getAnnotation(Ignore.class) != null) { + sb.append("\n\n // Class has @Ignore set\n\n"); + } else { + + final Method[] methods = tclass.getMethods(); + + final ArrayList pretest = new ArrayList(); + final ArrayList posttest = new ArrayList(); + final TreeSet excepts = new TreeSet(); + + for (Method m : methods) { + if (m.getAnnotation(Before.class) != null) { + pretest.add(m); + excepts.addAll(getExceptions(m)); + } + if (m.getAnnotation(After.class) != null) { + posttest.add(m); + excepts.addAll(getExceptions(m)); + } + } + + sb.append(" private final ").append(sname).append(" test = new ").append(sname).append("();\n"); + + sb.append("\n @Override\n"); + sb.append(" public void setUp() throws Exception {\n"); + sb.append(" super.setUp();\n"); + sb.append(" // tests run when class starts...\n"); + sb.append(" org.jdom.test.util.UnitTestUtil.setAndroid();\n"); + sb.append(" System.setProperty(\"javax.xml.validation.SchemaFactory:http://www.w3.org/2001/XMLSchema\",\n"); + sb.append(" \"org.apache.xerces.jaxp.validation.XMLSchemaFactory\");\n"); + for (Method m : methods) { + if (m.getAnnotation(BeforeClass.class) != null) { + sb.append(" test.").append(m.getName()).append("();\n"); + } + } + sb.append(" }\n"); + + sb.append("\n @Override\n"); + sb.append(" public void tearDown() throws Exception {\n"); + sb.append(" super.tearDown();\n"); + sb.append(" // tests run when class completes...\n"); + for (Method m : methods) { + if (m.getAnnotation(AfterClass.class) != null) { + sb.append(" test.").append(m.getName()).append("();\n"); + } + } + sb.append(" }\n"); + + for (Method m : methods) { + Test tanno = null; + if ((tanno = m.getAnnotation(Test.class)) != null && m.getAnnotation(Ignore.class) == null) { + final String tname = getTestName(m); + sb.append("\n"); + sb.append(" public void ").append(tname).append("()").append(buildThrows(excepts, m)).append("{\n"); + for (Method pre : pretest) { + sb.append(" // pre test\n"); + sb.append(" test.").append(pre.getName()).append("();\n"); + } + if (posttest.isEmpty()) { + sb.append(" // actual test\n"); + if (tanno.expected() == Test.None.class) { + sb.append(" test.").append(m.getName()).append("();\n"); + } else { + sb.append(" try {\n"); + sb.append(" test.").append(m.getName()).append("();\n"); + sb.append(" org.jdom.test.util.UnitTestUtil.failNoException(").append(tanno.expected().getName()).append(".class);\n"); + sb.append(" } catch (").append(tanno.expected().getName()).append(" e) {\n"); + sb.append(" org.jdom.test.util.UnitTestUtil.checkException(").append(tanno.expected().getName()).append(".class, e);\n"); + sb.append(" }\n"); + } + } else { + sb.append(" try {\n"); + sb.append(" // actual test\n"); + if (tanno.expected() == Test.None.class) { + sb.append(" test.").append(m.getName()).append("();\n"); + } else { + sb.append(" try {\n"); + sb.append(" test.").append(m.getName()).append("();\n"); + sb.append(" org.jdom.test.util.UnitTestUtil.failNoException(").append(tanno.expected().getName()).append(".class);\n"); + sb.append(" } catch (Exception e) {\n"); + sb.append(" org.jdom.test.util.UnitTestUtil.checkException(").append(tanno.expected().getName()).append(".class, e);\n"); + sb.append(" }\n"); + } + sb.append(" } finally {\n"); + for (Method post : posttest) { + sb.append(" // post test\n"); + sb.append(" test.").append(post.getName()).append("();\n"); + } + sb.append(" }\n"); + + } + sb.append(" }\n"); + } + } + + } + + sb.append("\n}\n"); + final File outd = outf.getParentFile().getAbsoluteFile(); + if (!outd.isDirectory()) { + outd.mkdirs(); + } + FileWriter fw = new FileWriter(outf); + fw.write(sb.toString()); + fw.flush(); + fw.close(); + return ret; + } + + private String getTestName(final Method m) { + final String mname = m.getName(); + for (String mm : skipmethods) { + if (Pattern.matches(mm, mname)) { + return "/* Skip test on Android */ do_not_" + mname; + } + } + return mname.startsWith("test") ? mname : ("test_" + mname); + } + + private Set getExceptions(Method m) { + TreeSet hs = new TreeSet(); + if (m != null) { + for (Class ec : m.getExceptionTypes()) { + hs.add(ec.getName()); + } + } + return hs; + } + + private String buildThrows(Set current, Method m) { + final Set tothrow = getExceptions(m); + if (current != null) { + tothrow.addAll(current); + } + final Iterator it = tothrow.iterator(); + if (!it.hasNext()) { + return " "; + } + final StringBuilder sb = new StringBuilder(); + sb.append(" throws ").append(it.next()); + while (it.hasNext()) { + sb.append(", ").append(it.next()); + } + sb.append(" "); + return sb.toString(); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/beans/BeanMapper.java b/contrib/src/java/org/jdom/contrib/beans/BeanMapper.java new file mode 100644 index 0000000..99a2880 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/beans/BeanMapper.java @@ -0,0 +1,983 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin & Alex Chaffee. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.beans; + +import java.lang.reflect.*; +import java.util.*; +import java.beans.*; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Attribute; +import org.jdom.Namespace; + +/** + * Maps a JavaBean to an XML tree and vice versa. (Yes, it's yet + * another XML Data Binding solution.) Given a JavaBean, it will + * produce a JDOM tree whose elements correspond to the bean's + * property values. Given a JDOM tree, it will return a new instance + * of that bean whose properties have been set using the corresponding + * values in the JDOM tree.

+ * + * By default, it assumes each element maps to a property of the same + * name, subject to normal capitalization rules. That is, an element + * <foo> will map to the methods setFoo and getFoo. You can + * change this behavior by calling the various addMapping + * methods. For instance, to map a an element <date> to the + * property "birthDate" (using methods setBirthDate and getBirthDate), + * call

mapper.addMapping("birthDate", "date");
You can + * also map a property to an attribute, either of a child or of the + * parent (see JavaDoc for + * + * addMapping(String property, String element, String attribute) + * for details).

+ * + * During Bean -> JDOM conversion, if a BeanInfo object is found, it + * will be respected. See JavaDoc for java.beans.Introspector.

+ * + * If a given property, element, or attribute is to be skipped + * (ignored) during conversion, call the appropriate ignore method + * (ignoreProperty, ignoreElement, or ignoreAttribute). This is also + * appropriate if your bean has multiple accessors (properties) for + * the same underlying data.

+ * + * Support for Namespaces is rudimentary at best and has not been + * well-tested; if you specify a Namespace then all created elements + * and attributes will be in that namespace (or all elements not in + * that namespace will be skipped, for JDOM->Bean mapping).

+ * + * If a bean property type is a Java array, its items will be mapped + * to multiple child elements of the bean element (multiple children + * at the base level, not one child). This is to provide an easier + * transition for XML documents whose elements contain multiple + * children with the same name.

+ * + * Please try this out on your own beans. If there is a case that + * fails to do what you like (for instance, properties with custom + * class types), let me know and I'll try to work it out.

+ * + *

TODO:
+ * support list properties (collections other than Java arrays)
+ * allow lists/arrays to map to either scattered elements or a single nested element
+ * sort XML elements in some order other than random BeanInfo order
+ * support for BeanInfoSearchPaths
+ * multiple packages searched for beans
+ * better support for Namespaces (hard)
+ * allow stringconverter to map object -> string (not just string -> object)
+ * id/idref support for cyclical references
+ * more type converters
+ * custom property converters
+ * known issue: inner class bean can't be found jdom->bean (workaround: define mapping containing class name)
+ * 
+ * + *

Example:

+ *

TestBean

+ *
+ * public class TestBean implements java.io.Serializable {
+ *    public String getName() { ... }
+ *    public int getAge() { ... }
+ *    public Date getBirthdate() { ... }
+ *    public TestBean getFriend() { ... }
+ *    public void setName(String name) { ... }
+ *    public void setAge(int age) { ... }
+ *    public void setBirthdate(Date birthdate) { ... }
+ *    public void setFriend(TestBean friend) { ... }
+ *    public String toString() { ... }
+ * }
+ * 
+ *

XML Representation

+ *
 <testBean>
+ *   <dob age="31">Fri Aug 08 00:00:00 EDT 1969</dob>
+ *   <name>Alex</name>
+ *   <friend>
+ *     <testBean>
+ *       <dob age="25">Thu May 01 00:00:00 EDT 1975</dob>
+ *       <name>Amy</name>
+ *     </testBean>
+ *   </friend>
+ * </testBean>
+ * 
+ *

Mapping code

+ *
 BeanMapper mapper = new BeanMapper();
+ * mapper.addMapping("birthdate", "dob");        // element mapping
+ * mapper.addMapping("age", "dob", "age");        // attribute mapping
+ * mapper.setBeanPackage("org.jdom.contrib.beans");
+ * 
+ *

Converting Bean to JDOM

+ *
Document doc = mapper.toDocument(alex);
+ *

Converting JDOM to Bean

+ *
TestBean alex = mapper.toBean(doc);
+ * + * @author Alex Chaffee (alex@jguru.com) + **/ + +@SuppressWarnings("javadoc") +public class BeanMapper { + + protected String beanPackage; + protected Namespace namespace; + protected boolean ignoreMissingProperties = false; + protected boolean ignoreNullProperties = true; + protected List mappings = new ArrayList(); + protected StringConverter stringconverter = new StringConverter(); + + /** + * Default constructor. If you are only doing bean -> XML + * mapping, you may use the mapper immediately. Otherwise, you + * must call setBeanPackage. + **/ + public BeanMapper() + { + } + + // Properties + + /** + * @param beanPackage the name of the package in which to find the + * JavaBean classes to instantiate + **/ + public void setBeanPackage(String beanPackage) + { + this.beanPackage = beanPackage; + } + + /** + * Use this namespace when creating the XML element and all + * child elements. + **/ + public void setNamespace(Namespace namespace) + { + this.namespace = namespace; + } + + /** + * Get the object responsible for converting a string to a known + * type. Once you get this, you can call its setFactory() method + * to add a converter for a custom type (for which toString() does + * not suffice). The default string converter has a factory that + * recognizes several date formats (including ISO8601 - + * e.g. "2000-09-18 18:51:22-0600" or some substring thereof). + **/ + public StringConverter getStringConverter() { + return stringconverter; + } + + /** + * Set a custom string converter. + **/ + public void setStringConverter(StringConverter stringconverter) { + this.stringconverter = stringconverter; + } + + /** + * In mapping from Bean->JDOM, if we encounter an property with a + * null value, should + * we ignore it or add an empty child element/attribute (default: true)? + * @param b true = ignore, false = empty element + **/ + public void setIgnoreNullProperties(boolean b) { + ignoreNullProperties = b; + } + + /** + * In mapping from JDOM->Bean, if we encounter an element or + * attribute without a corresponding property in the bean, should + * we ignore it or throw an exception (default: false)? + * @param b true = ignore, false = throw exception + **/ + public void setIgnoreMissingProperties(boolean b) { + ignoreMissingProperties = b; + } + + + // Bean -> JDOM Mapping + + /** + * Converts the given bean to a JDOM Document. + * @param bean the bean from which to extract values + **/ + public Document toDocument(Object bean) throws BeanMapperException { + return toDocument(bean, null); + } + + /** + * Converts the given bean to a JDOM Document. + * @param bean the bean from which to extract values + * @param name the name of the root element (null => use bean class name) + **/ + public Document toDocument(Object bean, String elementName) + throws BeanMapperException { + Element root = toElement(bean, elementName); + Document doc = new Document(root); + return doc; + } + + /** + * Converts the given bean to a JDOM Element. + * @param bean the bean from which to extract values + * @param elementName the name of the element (null => use bean class name) + **/ + public Element toElement(Object bean) throws BeanMapperException + { + return toElement(bean, null); + } + + /** + * Converts the given bean to a JDOM Element. + * @param bean the bean from which to extract values + * @param elementName the name of the element (null => use bean class name) + **/ + public Element toElement(Object bean, String elementName) + throws BeanMapperException { + BeanInfo info; + try { + // cache this? + info = Introspector.getBeanInfo(bean.getClass()); + } + catch (IntrospectionException e) { + throw new BeanMapperException("Mapping bean " + bean, e); + } + + // create element + Element element; + String beanname; + if (elementName != null) { + element = createElement(elementName); + } + else { + Class beanclass = info.getBeanDescriptor().getBeanClass(); + beanname = unpackage(beanclass.getName()); + element = createElement(beanname); + } + + // get all properties, set as child-elements + PropertyDescriptor[] properties = info.getPropertyDescriptors(); + for (int i=0; i type = value.getClass(); + String classname = type.getName(); + + // todo: allow per-type callback to convert (if toString() is + // inadequate) -- extend stringconverter? + if (classname.startsWith("java.lang.") || + classname.equals("java.util.Date") + ) + { + result = value.toString(); + } + else if (type.isArray()) { + // it's an array - use java.lang.reflect.Array to extract + // items (or wrappers thereof) + List list = new ArrayList(); + for (int i=0; i it = ((List)value).iterator(); + it.hasNext(); ) + { + Object item = it.next(); + if (child == null) { + child = createElement(elementName); + parent.addContent(child); + } + setElementValue(propertyName, elementName, parent, child, item); + // this'll be weird if it's an array of arrays + child = null; + } + } + else + throw new BeanMapperException( + "Unknown result type for property " + propertyName + ": " + value); + } + + // JDOM -> Bean + + /** + * Converts the given JDOM Document to a bean + * @param document the document from which to extract values + **/ + public Object toBean(Document document) throws BeanMapperException { + return toBean(document.getRootElement()); + } + + public Object toBean(Element element) throws BeanMapperException { + + Object bean = instantiateBean(element.getName()); + + Mapping mapping; + String propertyName; + + Set alreadySet = new HashSet(); + + // map Attributes of parent first + if (element.hasAttributes()) { + for (Attribute attribute : element.getAttributes()) { + debug("Mapping " + attribute); + mapping = getMappingForAttribute(null, attribute.getName()); + propertyName = (mapping==null) ? + attribute.getName() : mapping.property; + setProperty(bean, propertyName, attribute.getValue()); + } + } + + // map child Elements + //debug(element.toString() + " has " + children.size() + " children"); + for (Element child : element.getChildren()) { + debug("Mapping " + child); + + mapping = getMappingForElement(child.getName()); + propertyName = (mapping==null) ? child.getName() : mapping.property; + + // set bean property from element + PropertyDescriptor property = + findPropertyDescriptor(bean, propertyName); + if (property != null) { + if (!alreadySet.contains(child.getName())) { + if (property.getPropertyType().isArray()) + setProperty(bean, property, element, child); + else + setProperty(bean, property, element, child); + } + } + + // Now map all attributes of this child + for (Attribute attribute : child.getAttributes()) { + debug("Mapping " + attribute); + mapping = getMappingForAttribute(child.getName(), + attribute.getName()); + propertyName = (mapping==null) ? + attribute.getName() : mapping.property; + setProperty(bean, propertyName, attribute.getValue()); + } // for attributes + + alreadySet.add(child.getName()); + + } // for children + + return bean; + } // toBean + + + /** + * return a fresh new object of the appropriate bean type for + * the given element name. + * @return the bean + **/ + protected Object instantiateBean(String elementName) + throws BeanMapperException { + // todo: search multiple packages + String className = null; + Class beanClass; + try { + Mapping mapping = getMappingForElement(elementName); + if (mapping != null && + mapping.type != null) { + beanClass = mapping.type; + } + else { + className = getBeanClassName(beanPackage, elementName); + beanClass = Class.forName(className); + } + Object bean = beanClass.newInstance(); + return bean; + } + catch (ClassNotFoundException e) { + throw new BeanMapperException("Class " + className + + " not found instantiating " + elementName + + " - maybe you need to add a mapping, or add a bean package", e); + } + catch (Exception e) { + throw new BeanMapperException("Instantiating " + elementName, e); + } + } + + protected String getBeanClassName(String pbeanPackage, String elementName) { + return (pbeanPackage == null ? "" : (pbeanPackage + ".")) + + Character.toUpperCase(elementName.charAt(0)) + + elementName.substring(1); + } + + protected PropertyDescriptor findPropertyDescriptor(Object bean, + String propertyName) + throws BeanMapperException { + try { + // cache this? + BeanInfo info = Introspector.getBeanInfo(bean.getClass()); + PropertyDescriptor[] properties = info.getPropertyDescriptors(); + for (int i=0; i param = params[0]; + if (param != property.getPropertyType()) + debug("Weird: setter takes " + param + ", property is " + + property.getPropertyType()); + + debug("Invoking setter: " + setter.getName() + + "(" + valueObject + ")"); + setter.invoke(bean, new Object[] { valueObject }); + + return true; + } + catch (BeanMapperException e) { + throw e; + } + catch (Exception e) { + throw new BeanMapperException("Setting property " + + property.getName() + "=" + value + " in " + bean.getClass(), e); + } + } + + protected Object convertString(String value, Class type) { + if (value == null) + return null; + if (type == String.class) + return value; + return stringconverter.parse(value, type); + } + + protected Object convertJDOMValue(Object value, Class type) + throws BeanMapperException { + Object valueObject; + + // Null value + if (value == null) + valueObject = null; + + // String value + else if (value instanceof String) { + valueObject = convertString((String)value, type); + } + + // Element value + else if (value instanceof Element) { + Element element = (Element)value; + + // if the setter actually takes a JDOM element, pass it + if (type == Element.class) + valueObject = value; + + // no children, must be a text node + else if (element.getChildren().isEmpty()) + { + valueObject = convertString(element.getText(), type); + } + + // we have to convert it into a bean + else if (element.getChildren().size() == 1) { + + // Make a recursive call to this BeanMapper to map + // the child element + + // Note that toBean could return a subclass of the + // property type, so just let it figure out the + // right type + + valueObject = toBean(element.getChildren().get(0)); + } + else { + // element with multiple children -- must be an + // array property + throw new BeanMapperException( + "Mapping of multiple child elements not implemented for " + + element.getName()); + } + } + else { + throw new BeanMapperException("Can't map unknown type: " + + value.getClass() + "=" + value); + } + return valueObject; + } // convert JDOMValue + + /** + * @return an array of the appropriate type + **/ + protected Object buildArray(PropertyDescriptor property, List children) + throws BeanMapperException { + Class arrayClass = property.getPropertyType(); + + Class itemClass = arrayClass.getComponentType(); + + if (itemClass == null) { + throw new BeanMapperException("Can't instantiate array of type " + + arrayClass); + } + + // use java.lang.reflect.Array + Object array = Array.newInstance(itemClass, children.size()); + + // fill it + for (int i = 0; i parent element + * @param attribute the name of the attribute. null => set as element + **/ + public void addMapping(String property, String element, String attribute) { + addMapping(new Mapping(property, null, element, attribute)); + } + + /** + * Map a property name to an element or an attribute. Can also + * specify which bean class to instantiate (applies to JDOM->Bean + * mapping). + * + * @param property the name of the property. null => look for property + * with same name as the element + * @param type Always convert this element name to this class. + * null => look for a bean with the same name as the element + * @param element the name of the element containing the attribute. + * null => parent element + * @param attribute the name of the attribute. null => set as element + **/ + public void addMapping(String property, Class type, + String element, String attribute) { + addMapping(new Mapping(property, type, element, attribute)); + } + + public void addMapping(Mapping mapping) { + mappings.add(mapping); + } + + public Mapping getMappingForProperty(String property) { + for (Mapping m : mappings) { + if (m.property != null && m.property.equals(property)) { + return m; + } + } + return null; + } + + public Mapping getMappingForElement(String element) { + for (Mapping m : mappings) { + if (m.element.equals(element)) { + return m; + } + } + return null; + } + + public Mapping getMappingForAttribute(String element, String attribute) { + for (Mapping m : mappings) { + if (m.element != null && + m.attribute != null && + m.element.equals(element) && + m.attribute.equals(attribute)) + { + return m; + } + } + return null; + } + + public class Mapping { + public String property; + public Class type; + public String element; + public String attribute; + + /** + * @param property the name of the property. null => look for + * property with same name as the element + * @param type Always convert this element name to this class. + * null => look for a bean with the same name as the element + * @param element the name of the element containing the attribute. + * null => parent element + * @param attribute the name of the attribute. null => set as element + **/ + public Mapping(String property, Class type, + String element, String attribute) { + this.property = property; + this.type = type; + this.element = element; + this.attribute = attribute; + } + + + } + + // Hiding + + protected Set ignoredProperties = new HashSet(); + protected Set ignoredElements = new HashSet(); + protected Set ignoredAttributes = new HashSet(); + + public void ignoreProperty(String property) { + ignoredProperties.add(property); + } + + public boolean isIgnoredProperty(String property) { + return ignoredProperties.contains(property); + } + + public void ignoreElement(String element) { + ignoredElements.add(element); + } + + public boolean isIgnoredElement(String element) { + return ignoredElements.contains(element); + } + + static protected String toAttributeString(String element, + String attribute) { + return (element == null ? "." : element) + + "/@" + attribute; + } + + public void ignoreAttribute(String element, String attribute) { + ignoredAttributes.add(toAttributeString(element, attribute)); + } + + public boolean isIgnoredAttribute(String element, String attribute) { + return ignoredAttributes.contains( + toAttributeString(element, attribute)); + } + + // Utilities + + protected Element createElement(String elementName) { + return namespace == null ? new Element(elementName) : + new Element(elementName, namespace); + } + + protected static String unpackage(String classname) { + int dot = Math.max(classname.lastIndexOf("."), + classname.lastIndexOf("$")); + if (dot > -1) { + classname = classname.substring(dot+1); + } + classname = Introspector.decapitalize(classname); + return classname; + } + + public static int debug = 0; + protected static void debug(String msg) { + if (debug > 0) + System.err.println("BeanMapper: " + msg); + } +} + diff --git a/contrib/src/java/org/jdom/contrib/beans/BeanMapperException.java b/contrib/src/java/org/jdom/contrib/beans/BeanMapperException.java new file mode 100644 index 0000000..a152927 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/beans/BeanMapperException.java @@ -0,0 +1,83 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.beans; + + +/** + * + * @author Alex Chaffee (alex@jguru.com) + **/ + +@SuppressWarnings("javadoc") +public class BeanMapperException extends Exception { + + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + public BeanMapperException(Exception rootCause) { + super(rootCause); + } + + public BeanMapperException(String message, Exception rootCause) { + super(message, rootCause); + } + + public BeanMapperException(String message) { + super(message); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/beans/DateUtils.java b/contrib/src/java/org/jdom/contrib/beans/DateUtils.java new file mode 100644 index 0000000..3400685 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/beans/DateUtils.java @@ -0,0 +1,216 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +// Based on code Copyright (c) 1998-2000 Alex Chaffee and Purple Technology. + +package org.jdom.contrib.beans; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.text.*; + +/** + * @author Alex Chaffee (alex@jguru.com) + **/ +@SuppressWarnings("javadoc") +public class DateUtils { + + public static boolean debug; + + /** + * Tries to parse the date according to several different formats. + *

+ * BUG in TimeZone processing -- Calendar class always screws up the time when a TZ is set -- so ignored for now. + * + * @return null if not parseable + **/ + @SuppressWarnings("deprecation") + public static Date parseDate(String s) { + + // some standard date format + try { + // this is deprecated, but it still parses more + // formats than DateFormat.parse(String) + return new Date(s); + } + catch (IllegalArgumentException dfe) { + // do nothing... we try to recover. + } + + // some other (?) standard date format + try { + return DateFormat.getDateInstance().parse(s); + } + catch (ParseException pe) { + // OK, this is challenging, but we try the next option. + } + + // a single int = msec since 1970 + try { + return new Date(Long.parseLong(s)); + } + catch (NumberFormatException nfe) { + // Getting rediculous now... + } + + ISO8601 iso = parseISO8601(s); + if (iso != null) { + Calendar cal = Calendar.getInstance(); + + cal.set(Calendar.YEAR, iso.year); + cal.set(Calendar.MONTH, iso.month - 1); + cal.set(Calendar.DAY_OF_MONTH, iso.day); + cal.set(Calendar.HOUR, iso.hour + 12); // ??? TZ bug again? + cal.set(Calendar.MINUTE, iso.min); + cal.set(Calendar.SECOND, iso.sec); + + return cal.getTime(); // why the hell does getTime() return a Date? + + + } // if iso + + return null; + } // parseDate + + public static class ISO8601 { + public int year; + public int month; + public int day; + public int hour; + public int min; + public int sec; + public int frac; + public String tz; + } + + protected static String reISO8601 = + "(\\d\\d\\d\\d)(-(\\d\\d)(-(\\d\\d))?)?" + + "([T| ]?" + + "(\\d\\d):(\\d\\d)(:((\\d\\d)(\\.(\\d+))?)?)?" + + "(Z|([+-]\\d\\d:\\d\\d)|([A-Z]{3}))?)?"; + + public static ISO8601 parseISO8601(String s) { + // ISO 8601 datetime: http://www.w3.org/TR/NOTE-datetime + // e.g. 1997-07-16T19:20:30.45+01:00 + // additions: "T" can be a space, TZ can be a three-char code, TZ can be missing + try { + Pattern pat = Pattern.compile(reISO8601); + Matcher re = pat.matcher(s); + if (re.matches()) { + if (debug) + showParens(re); + + ISO8601 iso = new ISO8601(); + iso.year = toInt(re.group(1)); + iso.month = toInt(re.group(3)); + iso.day = toInt(re.group(5)); + iso.hour = toInt(re.group(7)); + iso.min = toInt(re.group(8)); + iso.sec = toInt(re.group(11)); + iso.frac = toInt(re.group(13)); + iso.tz = re.group(14); + + if (debug) { + System.out.println("year='" + iso.year + "'"); + System.out.println("month='" + iso.month + "'"); + System.out.println("day='" + iso.day + "'"); + System.out.println("hour='" + iso.hour + "'"); + System.out.println("min='" + iso.min + "'"); + System.out.println("sec='" + iso.sec + "'"); + System.out.println("frac='" + iso.frac + "'"); + System.out.println("tz='" + iso.tz + "'"); + } + + return iso; + } + } // try + catch (PatternSyntaxException ree) { + ree.printStackTrace(); + } + return null; + } + + public static int toInt(String x) { + if (x == null) return 0; + try { + return Integer.parseInt(x); + } + catch (NumberFormatException e) { + return 0; + } + } + + /** + * Dump parenthesized subexpressions found by a regular expression matcher object + * @param r Matcher object with results to show + */ + static void showParens(Matcher r) + { + // Loop through each paren + for (int i = 0; i < r.groupCount(); i++) + { + // Show paren register + System.out.println("$" + i + " = " + r.group(i)); + } + } + + public static void main(String[] args) { + debug = true; + for (int i=0; i. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.beans; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaderJDOMFactory; +import org.jdom.input.sax.XMLReaderSAX2Factory; +import org.jdom.output.XMLOutputter2; + +// todo: +// weak references and/or timeout cache +// load from URL (instead of just from file) +// pathname normalization (remove ./ and foo/../ and so forth) +// allow DOM builder or arbitrary builder + +/** + * A light wrapper on the JDOM library that you use to load in a file + * and turn it into a JDOM Document. It also keeps a cache of + * already-parsed files, and checks to see if they've changed on disk, + * and reloads if they have. (I know there's some sort of swap-out or + * weak-reference stuff either in JDOM or coming soon, so this may be + * a redundant feature.) + *

+ * + *

Usage from Java:

+ * + *
+ * JDOMBean jdom = new JDOMBean(); // or new JDOMBean("com.foo.saxparser")
+ * jdom.setFileRoot("/path/to/my/xml/documents/");
+ * Document doc = jdom.getDocument("foo.xml");
+ * Element root = jdom.getRootElement("foo.xml");
+ * 
+ * + *

Usage from JSP:

+ * + *
+ *  
+ * <jsp:useBean id="jdom" class="JDOMBean" scope="application">
+ *  <% jdom.setFileRoot(application.getRealPath("")); %>
+ * </jsp:useBean>
+ *  
+ * or
+ *  
+ * <jsp:useBean id="jdom" class="JDOMBean" scope="application">
+ *  <jsp:setProperty name="jdom" property="fileRoot"
+ * +value='<%=application.getRealPath("")%>' />
+ * </jsp:useBean>
+ *                                           
+ * then 
+ *  
+ * <%
+ * Element root = jdom.getRootElement("foo.xml");
+ * %>
+ * Bar: <%=root.getChild("bar").getContent()%>
+ * 
+ * + * @author Alex Chaffee [alex@jguru.com] + **/ +@SuppressWarnings("javadoc") +public class JDOMBean { + + /** Default SAX parser class to use */ + private static final String DEFAULT_PARSER = + "org.apache.xerces.parsers.SAXParser"; + + /** SAX parser class to use */ + private String parser; + + /** {@link SAXBuilder} instance to use */ + private SAXBuilder builder; + + /** file cache **/ + private Map files = new HashMap(); + +// /** where to locate files **/ +// private File fileRoot; + + /** + * default constructor, uses "org.apache.xerces.parsers.SAXParser" + **/ + public JDOMBean() { + setParser(DEFAULT_PARSER); + } + + /** + * @param parser String name of driver class to use. + **/ + public JDOMBean(String parser) { + setParser(parser); + } + + /** + *

+ * This will create an instance of {@link SAXBuilder} + * for use in the rest of this program. + *

+ * + * @param parser String name of SAX parser class to use. + */ + public void setParser(String parser) { + this.parser = parser; + XMLReaderJDOMFactory fac = new XMLReaderSAX2Factory(false, parser); + builder = new SAXBuilder(fac); + } + + /** + * @return name of SAX parser class being used + **/ + public String getParser() { + return parser; + } + + /** + * All files are fetched relative to this path + * @param root the path (absolute or relative) to the document root + **/ + public void setFileRoot(String root) { +// if (!root.endsWith("/")) { +// root = root + "/"; +// } +// this.fileRoot = new File(root); +// System.out.println("fileroot=" + fileRoot); + } + + /** + * @return the path (absolute or relative) to the document root + **/ + public String getFileRoot() { +// if (fileRoot == null) { + return null; +// } +// return fileRoot.getAbsolutePath(); + } + + /** + * Load a file, parse it with JDOM, return a org.jdom.Document. + * If the file has already been parsed, return the previously + * cached object. If the file has changed, ignore the previously + * parsed version and reload.

+ * + * Note that this never unloads a document, so is unsuitable for + * long-term server-side use for a constantly changing set of + * files. Todo: use weak references or cache timeouts.

+ * + * Also does not do secure checking on file requested, so if + * there's no root, and the parameter starts with a "/", this + * could conceivably access files you don't want accessed. So be + * careful out there. + * + * @param filename the file to load, relative to file root + * @return a JDOM Document corresponding to the given filename + **/ + public Document getDocument(String filename) throws JDOMException, IOException { + FileInfo info = files.get(filename); + File file = getFile(filename); + if (info == null || + info.modified < file.lastModified()) + { + Document doc = builder.build(file); + info = new FileInfo(filename, file.lastModified(), doc); + files.put(filename, info); + } + return info.document; + } + + /** + * Convenience method, calls getDocument(filename).getRootElement() + **/ + public Element getRootElement(String file) throws JDOMException, IOException { + Document doc = getDocument(file); + if (doc != null) return doc.getRootElement(); + return null; + } + + private File getFile(String filename) { +// if (fileRoot == null) { + return new File(filename); +// } +// return new File(fileRoot, filename); + } + + /** + * Information stored in the cache + **/ + class FileInfo { + String name; + long modified; + Document document; + public FileInfo(String name, long modified, Document document) { + this.name = name; + this.modified = modified; + this.document = document; + } + } + + // Usage: java JDOMBean [-parser com.foo.parser] file1.xml file2.xml + // Fetches and prints files + public static void main(String[] args) throws IOException, JDOMException { + int i=0; + JDOMBean bean; + if (args[i].equals("-parser")) { + ++i; + bean = new JDOMBean(args[i]); + i++; + } + else { + bean = new JDOMBean(); + } + + XMLOutputter2 out = new XMLOutputter2(); + + for (; i. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.beans; + +import java.lang.reflect.*; +import java.util.*; + +/** + * Converts a String into a given object type. + * + * @author Alex Chaffee (alex@jguru.com) + **/ + +@SuppressWarnings("javadoc") +public class StringConverter { + + public static interface Factory { + public Object instantiate(String string); + } + + public static class DateFactory implements Factory + { + @Override + public Object instantiate(String string) { + return DateUtils.parseDate(string); + } + } + + protected Map, Factory> factories = new HashMap, Factory>(); + + public StringConverter() { + try { + factories.put( Class.forName("java.util.Date"), + new DateFactory() ); + } + catch (ClassNotFoundException e) { + // do nothing + } + } + + public void setFactory(Class type, Factory factory) { + factories.put(type, factory); + } + + protected static Class[] argString = new Class[] { String.class }; + + public Object parse(String string, Class type) + { + // if it's a string, return it + if (type == String.class) { + return string; + } + + // if we have a Factory for it + Factory factory = factories.get(type); + if (factory != null) { + return factory.instantiate(string); + } + + // if it's a primitive, convert to wrapper (???) + if (type == short.class) type = Short.class; + if (type == int.class) type = Integer.class; + if (type == long.class) type = Long.class; + if (type == boolean.class) type = Boolean.class; + if (type == char.class) type = Character.class; + if (type == byte.class) type = Byte.class; + + // last ditch: see if the class has a String Factory + try { + Constructor c = type.getConstructor(argString); + if (c != null) { + return c.newInstance( new Object[] { string } ); + } + } + catch (NoSuchMethodException e) { + // ignore & fall through + } + catch (Exception e) { + System.err.println("Couldn't instantiate " + type + "(" + string + ")"); + e.printStackTrace(); + } + + return null; + } +} diff --git a/contrib/src/java/org/jdom/contrib/beans/TestBean.java b/contrib/src/java/org/jdom/contrib/beans/TestBean.java new file mode 100644 index 0000000..fe3323a --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/beans/TestBean.java @@ -0,0 +1,145 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.beans; + +import org.jdom.*; +import org.jdom.output.*; +import java.util.*; + +@SuppressWarnings("javadoc") +public class TestBean implements java.io.Serializable { + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + private String name; + private int age; + private Date birthdate; + private TestBean friend; + + public String getName() { + return name; + } + public int getAge() { + return age; + } + public Date getBirthdate() { + return birthdate; + } + public TestBean getFriend() { + return friend; + } + + public void setName(String name) { + this.name = name; + } + public void setAge(int age) { + this.age = age; + } + public void setBirthdate(Date birthdate) { + this.birthdate = birthdate; + } + public void setFriend(TestBean friend) { + this.friend = friend; + } + + @Override + public String toString() { + return "TestBean[name='" + name + "', age=" + age + ", birthdate=" + birthdate + ", friend=" + friend + "]"; + } + + + // Test + + @SuppressWarnings("deprecation") + public static void main(String[] args) throws java.io.IOException { + + try { + BeanMapper mapper = new BeanMapper(); + mapper.addMapping("birthdate", "dob"); // element mapping + mapper.addMapping("age", "dob", "age"); // attribute mapping + + mapper.setBeanPackage("org.jdom.contrib.beans"); + + // test bean->jdom + + TestBean alex = new TestBean(); + alex.setName("Alex"); + alex.setAge(31); + alex.setBirthdate(new Date(69, 7, 8)); + + TestBean amy = new TestBean(); + amy.setName("Amy"); + amy.setAge(25); + amy.setBirthdate(new Date(75, 4, 1)); + + alex.setFriend(amy); + + Document doc = mapper.toDocument(alex); + XMLOutputter2 o = new XMLOutputter2(Format.getPrettyFormat()); + o.output(doc, System.out); + System.out.println(); + + // test jdom->bean + TestBean test2 = (TestBean)mapper.toBean(doc); + System.out.println(test2); + } + catch (BeanMapperException e) { + e.printStackTrace(); + } + } + +} diff --git a/contrib/src/java/org/jdom/contrib/beans/TestIndexed.java b/contrib/src/java/org/jdom/contrib/beans/TestIndexed.java new file mode 100644 index 0000000..9e38fc5 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/beans/TestIndexed.java @@ -0,0 +1,166 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.beans; + +import org.jdom.*; +import org.jdom.output.*; +import java.util.*; +import java.beans.*; + +@SuppressWarnings("javadoc") +public class TestIndexed implements java.io.Serializable { + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + private String name; + private List toppings = new ArrayList(); + private int[] measurements = new int[]{36, 24, 38}; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getTopping(int i) { + return toppings.get(i); + } + + public void setTopping(int i, String topping) { + while (i >= toppings.size()) { + toppings.add(null); + } + toppings.set(i, topping); + } + + public String[] getTopping() { + String[] a = new String[toppings.size()]; + for (int i = 0; i < toppings.size(); ++i) { + a[i] = toppings.get(i); + } + return a; + } + + public void setTopping(String[] x) { + if (x != null) + toppings = Arrays.asList(x); + } + + public int[] getMeasurements() { + return measurements; + } + + public void setMeasurements(int[] measurements) { + this.measurements = measurements; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("TestIndexed[name='" + name + "'"); + for (int i = 0; i < toppings.size(); ++i) { + buf.append(", topping=" + toppings.get(i)); + } + buf.append(", measurements="); + for (int i = 0; i < toppings.size(); ++i) { + buf.append(" " + measurements[i]); + } + buf.append("]"); + return buf.toString(); + } + + public static void main(String[] args) throws java.beans.IntrospectionException, java.io.IOException, BeanMapperException { + BeanMapper mapper = new BeanMapper(); + mapper.setBeanPackage("org.jdom.contrib.beans"); + + TestIndexed pizza = new TestIndexed(); + pizza.setName("Abominable"); + pizza.setTopping(0, "Anchovies"); + pizza.setTopping(1, "Steak Tartare"); + pizza.setTopping(2, "Raw Eggs"); + + BeanInfo info = Introspector.getBeanInfo(pizza.getClass()); + PropertyDescriptor[] ps = info.getPropertyDescriptors(); + for (int i = 0; i < ps.length; ++i) { + if (ps[i] instanceof IndexedPropertyDescriptor) { + IndexedPropertyDescriptor p = (IndexedPropertyDescriptor) ps[i]; + System.out.println("Indexed property " + p.getName() + " " + p.getShortDescription()); + System.out.println("Type = " + p.getPropertyType()); + System.out.println("Getter = " + p.getReadMethod()); + System.out.println("Indexed Getter = " + p.getIndexedReadMethod()); + } + } + + System.out.println("===== Testing toDocument()"); + Document doc = mapper.toDocument(pizza); + XMLOutputter2 o = new XMLOutputter2(Format.getPrettyFormat()); + o.output(doc, System.out); + System.out.println(); + + // test jdom->bean + System.out.println("===== Testing toBean()"); + TestIndexed test2 = (TestIndexed) mapper.toBean(doc); + System.out.println(test2); + + int[] test = new int[10]; + Class a1 = test.getClass(); + Class a2 = a1.getSuperclass(); + System.out.println("classes: " + a1 + " " + a2); + } +} diff --git a/contrib/src/java/org/jdom/contrib/dom/DOM.java b/contrib/src/java/org/jdom/contrib/dom/DOM.java new file mode 100644 index 0000000..7bd62da --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/DOM.java @@ -0,0 +1,184 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.EntityReference; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +import org.jdom.Content; + +/** + * Access JDOM Content using a (Read-Only) DOM model + * + * @author Rolf Lear + * + */ +public final class DOM { + /** + * Wrap a JDOM Document in a org.w3c.dom.Document instance. + * @param doc The JDOM Document to wrap. + * @return the wrapped Document + */ + public static final Document wrap(final org.jdom.Document doc) { + return wrap(doc, false); + } + + /** + * Wrap a JDOM Document in a org.w3c.dom.Document instance. + * @param doc The JDOM Document to wrap. + * @param scan Whether the entire document should be pre-processed + * @return the wrapped Document + */ + public static final Document wrap( + final org.jdom.Document doc, final boolean scan) { + final JDocument ret = new JDocument(doc); + if (scan) { + ret.scanAll(); + } + return ret; + } + + private static final JDocument makeDoc(final Content c) { + final org.jdom.Document doc = c.getDocument(); + return new JDocument(doc); + } + + /** + * Wrap a JDOM Element in a org.w3c.dom.Element instance. + * @param emt The JDOM Element to wrap. + * @return the wrapped Element + */ + public static final Element wrap(final org.jdom.Element emt) { + return makeDoc(emt).find(emt); + } + + + /** + * Wrap a JDOM Attribute in a org.w3c.dom.Attr instance. + * @param att The JDOM Attribute to wrap. + * @return the wrapped Attribute + */ + public static final Attr wrap(final org.jdom.Attribute att) { + final org.jdom.Document doc = att.getDocument(); + final JDocument jd = new JDocument(doc); + return jd.find(att); + } + + + + /** + * Wrap a JDOM Text in a org.w3c.dom.Text instance. + * @param text The JDOM Text to wrap. + * @return the wrapped Text + */ + public static final Text wrap(final org.jdom.Text text) { + return makeDoc(text).find(text); + } + + /** + * Wrap a JDOM CDATA in a org.w3c.dom.CDATASection instance. + * @param cdata The JDOM CDATA to wrap. + * @return the wrapped CDATA + */ + public static final CDATASection wrap(final org.jdom.CDATA cdata) { + return makeDoc(cdata).find(cdata); + } + + /** + * Wrap a JDOM EntityRef in a org.w3c.dom.EntityReference instance. + * @param eref The JDOM EntityRef to wrap. + * @return the wrapped EtityRef + */ + public static final EntityReference wrap(final org.jdom.EntityRef eref) { + return makeDoc(eref).find(eref); + } + + /** + * Wrap a JDOM ProcessingInstruction in a org.w3c.dom.ProcessingInstruction + * instance. + * @param pi The JDOM ProcessingInstruction to wrap. + * @return the wrapped ProcessingInstrction + */ + public static final ProcessingInstruction wrap( + final org.jdom.ProcessingInstruction pi) { + return makeDoc(pi).find(pi); + } + + /** + * Wrap a JDOM Comment in a org.w3c.dom.Comment instance. + * @param comment The JDOM Comment to wrap. + * @return the wrapped Comment + */ + public static final Comment wrap(final org.jdom.Comment comment) { + return makeDoc(comment).find(comment); + } + + /** + * Wrap a JDOM DocType in a org.w3c.dom.DocumentType instance. + * @param dt The JDOM DocType to wrap. + * @return the wrapped DocType + */ + public static final DocumentType wrap(final org.jdom.DocType dt) { + return makeDoc(dt).find(dt); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JAttribute.java b/contrib/src/java/org/jdom/contrib/dom/JAttribute.java new file mode 100644 index 0000000..375ead3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JAttribute.java @@ -0,0 +1,176 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import java.net.URISyntaxException; + +import org.w3c.dom.Attr; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.TypeInfo; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.Namespace; + +final class JAttribute extends JNamespaceAware implements Attr { + + final Attribute attribute; + public JAttribute(final JDocument topdoc, final JParent parent, final Attribute attribute, + final Namespace[] nstack) { + super(topdoc, parent, Node.ATTRIBUTE_NODE, nstack); + this.attribute = attribute; + } + + @Override + public final Object getWrapped() { + return attribute; + } + + @Override + public String getNodeName() { + return getName(); + } + @Override + public String getNodeValue() throws DOMException { + return getValue(); + } + @Override + public NodeList getChildNodes() { + return EMPTYLIST; + } + @Override + public Node getFirstChild() { + return null; + } + @Override + public Node getLastChild() { + return null; + } + @Override + public NamedNodeMap getAttributes() { + return null; + } + @Override + public boolean hasChildNodes() { + return false; + } + @Override + public String getNamespaceURI() { + return attribute.getNamespaceURI(); + } + @Override + public String getPrefix() { + return attribute.getNamespacePrefix(); + } + @Override + public String getLocalName() { + return attribute.getName(); + } + @Override + public boolean hasAttributes() { + return false; + } + @Override + public String getBaseURI() { + try { + return attribute.getParent().getXMLBaseURI().toASCIIString(); + } catch (final URISyntaxException e) { + throw new IllegalStateException("Unable to process URI", e); + } + } + @Override + public String getTextContent() throws DOMException { + return ""; + } + @Override + public String getName() { + return attribute.getQualifiedName(); + } + @Override + public boolean getSpecified() { + return true; + } + @Override + public String getValue() { + return attribute.getValue(); + } + + @Override + public Element getOwnerElement() { + return (Element)getParentNode(); + } + + @Override + public TypeInfo getSchemaTypeInfo() { + return TYPEINFO; + } + + @Override + public boolean isId() { + return attribute.getAttributeType() == AttributeType.ID; + } + + @Override + public void setValue(final String value) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + +} \ No newline at end of file diff --git a/contrib/src/java/org/jdom/contrib/dom/JCDATA.java b/contrib/src/java/org/jdom/contrib/dom/JCDATA.java new file mode 100644 index 0000000..5ba352a --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JCDATA.java @@ -0,0 +1,73 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.CDATASection; + +import org.jdom.Content; +import org.jdom.Namespace; + +final class JCDATA extends JText implements CDATASection { + public JCDATA(final JDocument topdoc, final JParent parent, + final Content content, final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNodeName() { + return "#cdata-section"; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JComment.java b/contrib/src/java/org/jdom/contrib/dom/JComment.java new file mode 100644 index 0000000..34f7ae8 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JComment.java @@ -0,0 +1,74 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.Comment; + +import org.jdom.Content; +import org.jdom.Namespace; + +final class JComment extends JSimpleCharacterContent implements Comment { + + public JComment(final JDocument topdoc, final JParent parent, final Content content, + final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNodeName() { + return "#comment"; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JContent.java b/contrib/src/java/org/jdom/contrib/dom/JContent.java new file mode 100644 index 0000000..6a42da3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JContent.java @@ -0,0 +1,135 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.jdom.Content; +import org.jdom.Namespace; + +abstract class JContent extends JNamespaceAware { + + private static final short getNodeType(final Content content) { + switch (content.getCType()) { + case CDATA: + return Node.CDATA_SECTION_NODE; + case Comment: + return Node.COMMENT_NODE; + case DocType: + return Node.DOCUMENT_TYPE_NODE; + case Element: + throw new IllegalStateException("JElement does not extend JContent"); + case EntityRef: + return Node.ENTITY_REFERENCE_NODE; + case ProcessingInstruction: + return Node.PROCESSING_INSTRUCTION_NODE; + case Text: + return Node.TEXT_NODE; + } + // this will never be run.... + return -1; + } + + protected final Content shadow; + + public JContent(final JDocument topdoc, final JParent parent, + final Content content, final Namespace[] nstack) { + super(topdoc, parent, getNodeType(content), nstack); + this.shadow = content; + } + + @Override + public final Object getWrapped() { + return shadow; + } + + @Override + public final NodeList getChildNodes() { + return EMPTYLIST; + } + + @Override + public final Node getFirstChild() { + return null; + } + + @Override + public final Node getLastChild() { + return null; + } + + @Override + public final boolean hasChildNodes() { + return false; + } + + @Override + public final boolean hasAttributes() { + return false; + } + + @Override + public final String getBaseURI() { + return parent.getBaseURI(); + } + + @Override + public final NamedNodeMap getAttributes() { + return null; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JDOMConfiguration.java b/contrib/src/java/org/jdom/contrib/dom/JDOMConfiguration.java new file mode 100644 index 0000000..7242f08 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JDOMConfiguration.java @@ -0,0 +1,101 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMStringList; + +class JDOMConfiguration implements DOMConfiguration { + + private static final DOMStringList EMPTYSTRINGS = new DOMStringList() { + + @Override + public String item(final int index) { + return null; + } + + @Override + public int getLength() { + return 0; + } + + @Override + public boolean contains(final String str) { + return false; + } + }; + + @Override + public void setParameter(final String name, final Object value) throws DOMException { + throw new DOMException(DOMException.NOT_FOUND_ERR, "No configuration!"); + } + + @Override + public Object getParameter(final String name) throws DOMException { + return null; + } + + @Override + public boolean canSetParameter(final String name, final Object value) { + return false; + } + + @Override + public DOMStringList getParameterNames() { + return EMPTYSTRINGS; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JDOMImplementation.java b/contrib/src/java/org/jdom/contrib/dom/JDOMImplementation.java new file mode 100644 index 0000000..6fa6e35 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JDOMImplementation.java @@ -0,0 +1,86 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; + +class JDOMImplementation implements DOMImplementation { + + @Override + public boolean hasFeature(final String feature, final String version) { + return false; + } + + @Override + public DocumentType createDocumentType(final String qualifiedName, + final String publicId, final String systemId) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "JDOM Wrapper"); + } + + @Override + public Document createDocument(final String namespaceURI, final String qualifiedName, + final DocumentType doctype) throws DOMException { + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "JDOM Wrapper"); + } + + @Override + public Object getFeature(final String feature, final String version) { + return null; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JDocType.java b/contrib/src/java/org/jdom/contrib/dom/JDocType.java new file mode 100644 index 0000000..6af1a88 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JDocType.java @@ -0,0 +1,117 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.DocumentType; +import org.w3c.dom.NamedNodeMap; + +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Namespace; + +class JDocType extends JSimpleContent implements DocumentType { + + public JDocType(final JDocument topdoc, final JParent parent, final Content content, + final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNodeName() { + return getName(); + } + + @Override + public String getNodeValue() throws DOMException { + return null; + } + + @Override + public String getTextContent() throws DOMException { + return null; + } + + @Override + public String getName() { + return ((DocType)shadow).getElementName(); + } + + @Override + public NamedNodeMap getEntities() { + return EMPTYMAP; + } + + @Override + public NamedNodeMap getNotations() { + return EMPTYMAP; + } + + @Override + public String getPublicId() { + return ((DocType)shadow).getPublicID(); + } + + @Override + public String getSystemId() { + return ((DocType)shadow).getSystemID(); + } + + @Override + public String getInternalSubset() { + return ((DocType)shadow).getInternalSubset(); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JDocument.java b/contrib/src/java/org/jdom/contrib/dom/JDocument.java new file mode 100644 index 0000000..a33bb33 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JDocument.java @@ -0,0 +1,549 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; + +import org.w3c.dom.Attr; +import org.w3c.dom.CDATASection; +import org.w3c.dom.Comment; +import org.w3c.dom.DOMConfiguration; +import org.w3c.dom.DOMException; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentFragment; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.EntityReference; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.ProcessingInstruction; +import org.w3c.dom.Text; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.DocType; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.filter.Filters; +import org.jdom.util.NamespaceStack; + +class JDocument extends JParent implements Document { + + private static final JDOMImplementation implementation = + new JDOMImplementation(); + + private static final JDOMConfiguration configuration = + new JDOMConfiguration(); + + private boolean allscanned = false; + private final JElement root; + private final JDocType doctype; + private final IdentityHashMap mapped = + new IdentityHashMap(); + private final HashMap idmap = new HashMap(); + + public JDocument(final org.jdom.Document shadow) { + super(null, null, shadow, Node.DOCUMENT_NODE, new Namespace[]{ + Namespace.NO_NAMESPACE, + Namespace.XML_NAMESPACE + }); + if (shadow == null) { + root = null; + doctype = null; + } else { + final JNamespaceAware[] kids = checkKids(); + JDocType dt = null; + JElement je = null; + for (final JNamespaceAware n : kids) { + if (n.getNodeType() == Node.DOCUMENT_TYPE_NODE) { + dt = (JDocType)n; + } + if (n.getNodeType() == Node.ELEMENT_NODE) { + je = (JElement)n; + } + } + root = je; + doctype = dt; + } + } + + public void scanAll() { + if (allscanned) { + return; + } + if (shadow == null) { + allscanned = true; + return; + } + allscanned = true; + final Iterator it = shadow.getDescendants(Filters.element()); + while (it.hasNext()) { + find(it.next()); + } + } + + public JElement find(final org.jdom.Element emt) { + final JNamespaceAware me = mapped.get(emt); + if (me != null) { + return (JElement)me; + } + final org.jdom.Element jp = emt.getParentElement(); + if (jp == null) { + // root level element (or detached). + final org.jdom.Document jd = emt.getDocument(); + // both may be null.... + if (jd != shadow) { + // we are from different documents... + throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, + "Element is not part of our document"); + } + // OK, we are a root level Element... let's map ourselves. + final NamespaceStack ns = new NamespaceStack(scope); + ns.push(emt); + final ArrayList added = new ArrayList(); + for (final Namespace ans : ns.addedForward()) { + added.add(ans); + } + final JElement je = new JElement(this, this, emt, ns.getScope(), + added.toArray(new Namespace[added.size()])); + mapped.put(emt, je); + checkID(je); + return je; + } + final JElement pnt = find(jp); + final NamespaceStack ns = new NamespaceStack(pnt.scope); + ns.push(emt); + final ArrayList added = new ArrayList(); + for (final Namespace ans : ns.addedForward()) { + added.add(ans); + } + final JElement ret = new JElement(this, pnt, emt, ns.getScope(), + added.toArray(new Namespace[added.size()])); + mapped.put(emt, ret); + checkID(ret); + return ret; + } + + public JAttribute find(final org.jdom.Attribute att) { + final JNamespaceAware me = mapped.get(att); + if (me != null) { + return (JAttribute)me; + } + final org.jdom.Element jp = att.getParent(); + final JParent pnt = jp == null ? this : find(jp); + final NamespaceStack ns = jp == null ? + new NamespaceStack() : new NamespaceStack(find(jp).scope); + ns.push(att); + final JAttribute ret = new JAttribute(this, pnt, att, ns.getScope()); + mapped.put(att, ret); + return ret; + } + + private JContent findContent(final org.jdom.Content content) { + final JNamespaceAware me = mapped.get(content); + if (me != null) { + return (JContent)me; + } + final org.jdom.Element jp = content.getParentElement(); + final JParent pnt = jp == null ? this : find(jp); + JContent ret = null; + switch (content.getCType()) { + case CDATA: + ret = new JCDATA(this, pnt, content, pnt.scope); + break; + case Comment: + ret = new JComment(this, pnt, content, pnt.scope); + break; + case DocType: + ret = new JDocType(this, pnt, content, pnt.scope); + break; + case EntityRef: + ret = new JEntityRef(this, pnt, content, pnt.scope); + break; + case ProcessingInstruction: + ret = new JProcessingInstruction(this, pnt, content, pnt.scope); + break; + case Text: + ret = new JText(this, pnt, content, pnt.scope); + break; + default: + throw new IllegalStateException( + "Other types should have their own methods."); + } + mapped.put(content, ret); + return ret; + } + + public JCDATA find(final CDATA content) { + return (JCDATA)findContent(content); + } + + public JDocType find(final DocType content) { + return (JDocType)findContent(content); + } + + public JProcessingInstruction find( + final org.jdom.ProcessingInstruction content) { + return (JProcessingInstruction)findContent(content); + } + + public JEntityRef find(final EntityRef content) { + return (JEntityRef)findContent(content); + } + + public JComment find(final org.jdom.Comment content) { + return (JComment)findContent(content); + } + + public JText find(final org.jdom.Text content) { + return (JText)findContent(content); + } + + private void checkID(final JElement je) { + final org.jdom.Element emt = (org.jdom.Element)(je.shadow); + if (emt.hasAttributes()) { + for (final Attribute a : emt.getAttributes()) { + if (a.getAttributeType() == AttributeType.ID) { + if (idmap.put(a.getValue(), je) != null) { + throw new DOMException(DOMException.INVALID_STATE_ERR, + "Multiple elements with id " + a.getValue()); + } + } + } + } + } + + @Override + public DocumentType getDoctype() { + return doctype; + } + + @Override + public Element getDocumentElement() { + return root; + } + + + + @Override + public NodeList getElementsByTagName(final String tagname) { + return getElementsByTagName(shadow, tagname); + } + + @Override + public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) { + return getElementsByTagNameNS(shadow, namespaceURI, localName); + } + + NodeList getElementsByTagName(final Parent xshadow, final String tagname) { + if (tagname == null) { + return EMPTYLIST; + } + final ArrayList enodes = new ArrayList(); + final boolean alltags = "*".equals(tagname); + + final Iterator it = + xshadow.getDescendants(Filters.element()); + + while (it.hasNext()) { + final org.jdom.Element e = it.next(); + if (alltags || tagname.equals(e.getQualifiedName())) { + enodes.add(find(e)); + } + } + + return new JNodeList(enodes); + } + + NodeList getElementsByTagNameNS(final Parent xshadow, final String namespaceURI, final String localName) { + if (localName == null) { + return EMPTYLIST; + } + if (namespaceURI == null) { + return EMPTYLIST; + } + + final boolean alluri = "*".equals(namespaceURI); + final boolean allname = "*".equals(localName); + + final ArrayList enodes = new ArrayList(); + + final Iterator it = + xshadow.getDescendants(Filters.element()); + + while (it.hasNext()) { + final org.jdom.Element e = it.next(); + if ((allname || localName.equals(e.getName())) && + (alluri || namespaceURI.equals(e.getNamespaceURI()))) { + enodes.add(find(e)); + } + } + + return new JNodeList(enodes); + } + + @Override + public Element getElementById(final String elementId) { + scanAll(); + return idmap.get(elementId); + } + + @Override + public String getDocumentURI() { + return shadow == null ? null : ((org.jdom.Document)shadow).getBaseURI(); + } + + @Override + public String getNodeName() { + return "#document"; + } + + @Override + public String getBaseURI() { + return shadow == null ? null : ((org.jdom.Document)shadow).getBaseURI(); + } + + /* ********************************************************* + * Everything after this point is 'dumb' code... (or error code) + * ********************************************************* */ + + @Override + public String getNodeValue() throws DOMException { + return null; + } + + @Override + public NamedNodeMap getAttributes() { + return null; + } + + @Override + public String getNamespaceURI() { + return null; + } + + @Override + public String getPrefix() { + return null; + } + + @Override + public String getLocalName() { + return null; + } + + @Override + public boolean hasAttributes() { + return false; + } + + @Override + public String getTextContent() throws DOMException { + return null; + } + + @Override + public String getXmlVersion() { + return "1.0"; + } + + @Override + public String getInputEncoding() { + return null; + } + + @Override + public String getXmlEncoding() { + return null; + } + + @Override + public boolean getXmlStandalone() { + return false; + } + + @Override + public final DOMImplementation getImplementation() { + return implementation; + } + + @Override + public final DOMConfiguration getDomConfig() { + return configuration; + } + + @Override + public final boolean getStrictErrorChecking() { + return false; + } + + @Override + public final void setStrictErrorChecking(final boolean strictErrorChecking) { + // nothing + } + + @Override + public final void setDocumentURI(final String documentURI) { + // Do nothing + } + + @Override + public final void normalizeDocument() { + // do nothing + } + + @Override + public final Node renameNode(final Node n, final String namespaceURI, final String qualifiedName) + throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final void setXmlStandalone(final boolean xmlStandalone) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final void setXmlVersion(final String xmlVersion) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Node adoptNode(final Node source) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Element createElement(final String tagName) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final DocumentFragment createDocumentFragment() { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Text createTextNode(final String data) { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Comment createComment(final String data) { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final CDATASection createCDATASection(final String data) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final ProcessingInstruction createProcessingInstruction(final String target, + final String data) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Attr createAttribute(final String name) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final EntityReference createEntityReference(final String name) + throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Node importNode(final Node importedNode, final boolean deep) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Element createElementNS(final String namespaceURI, final String qualifiedName) + throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Attr createAttributeNS(final String namespaceURI, final String qualifiedName) + throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JElement.java b/contrib/src/java/org/jdom/contrib/dom/JElement.java new file mode 100644 index 0000000..e1efc9a --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JElement.java @@ -0,0 +1,371 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.List; + +import org.jdom.filter2.AbstractFilter; +import org.w3c.dom.Attr; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.TypeInfo; + +import org.jdom.Attribute; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.filter2.Filters; + +class JElement extends JParent implements Element { + + private final class AttMap implements NamedNodeMap { + + private final Attr[] atts; + + public AttMap(final Attr[] atts) { + super(); + this.atts = atts; + } + + @Override + public Node item(final int index) { + if (index < 0 || index >= atts.length) { + return null; + } + return atts[index]; + } + + @Override + public Node getNamedItemNS(final String namespaceURI, final String localName) + throws DOMException { + if (namespaceURI == null || localName == null) { + return null; + } + for (int i = 0; i < atts.length; i++) { + if (namespaceURI.equals(atts[i].getNamespaceURI()) && + localName.equals(atts[i].getLocalName())) { + return atts[i]; + } + } + return null; + } + + @Override + public Node getNamedItem(final String name) { + if (name == null) { + return null; + } + for (int i = 0; i < atts.length; i++) { + if (name.equals(atts[i].getName())) { + return atts[i]; + } + } + return null; + } + + @Override + public int getLength() { + return atts.length; + } + + @Override + public Node setNamedItemNS(final Node arg) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node setNamedItem(final Node arg) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node removeNamedItemNS(final String namespaceURI, final String localName) + throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node removeNamedItem(final String name) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + } + + private NamedNodeMap attmap = null; + private final Namespace[] nsdec; + + public JElement(final JDocument topdoc, final JParent parent, + final Parent shadow, final Namespace[] nstack, + final Namespace[] ndec) { + super(topdoc, parent, shadow, Node.ELEMENT_NODE, nstack); + this. nsdec = ndec; + } + + @Override + public final String getNodeName() { + return getTagName(); + } + + @Override + public final String getNodeValue() throws DOMException { + return null; + } + + @Override + public final NamedNodeMap getAttributes() { + if (attmap == null) { + final org.jdom.Element emt = (org.jdom.Element)shadow; + if (emt.hasAttributes() || nsdec.length > 0) { + final List list = emt.getAttributes(); + final int sz = list.size(); + final Attr[] ja = new Attr[sz + nsdec.length]; + for (int i = 0; i < nsdec.length; i++) { + ja[i] = new JNamespace(topdoc, this, nsdec[i], scope); + } + for (int i = 0; i < sz; i++) { + ja[nsdec.length + i] = topdoc.find(list.get(i)); + } + attmap = new AttMap(ja); + } else { + attmap = EMPTYMAP; + } + } + return attmap; + } + + @Override + public final String getNamespaceURI() { + return ((org.jdom.Element)shadow).getNamespaceURI(); + } + + @Override + public final String getPrefix() { + return ((org.jdom.Element)shadow).getNamespacePrefix(); + } + + @Override + public final String getLocalName() { + return ((org.jdom.Element)shadow).getName(); + } + + @Override + public final String getBaseURI() { + try { + return ((org.jdom.Element)shadow).getXMLBaseURI().toASCIIString(); + } catch (final URISyntaxException e) { + throw new IllegalStateException("Broken base URI references.", e); + } + } + + @Override + public String getTextContent() throws DOMException { + final Iterator it = ((org.jdom.Element)shadow). + getDescendants(AbstractFilter.toFilter(Filters.fclass(org.jdom.Text.class))); + final StringBuilder sb = new StringBuilder(); + while (it.hasNext()) { + sb.append(it.next().getText()); + } + return sb.toString(); + } + + @Override + public String getTagName() { + return ((org.jdom.Element)shadow).getQualifiedName(); + } + + @Override + public TypeInfo getSchemaTypeInfo() { + // TODO Auto-generated method stub + return null; + } + + @Override + public NodeList getElementsByTagName(final String tagname) { + return topdoc.getElementsByTagName(shadow, tagname); + } + + @Override + public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) { + return topdoc.getElementsByTagNameNS(shadow, namespaceURI, localName); + } + + + /* ******************************************************** + * Attribute Access methods below + * ******************************************************** */ + + @Override + public final boolean hasAttributes() { + return ((org.jdom.Element)shadow).hasAttributes(); + } + + @Override + public boolean hasAttribute(final String name) { + final Attribute att = ((org.jdom.Element)shadow).getAttribute(name); + return att != null; + } + + @Override + public boolean hasAttributeNS(final String namespaceURI, final String localName) + throws DOMException { + final Attribute att = + ((org.jdom.Element)shadow).getAttribute( + localName, Namespace.getNamespace(namespaceURI)); + return att != null; + } + + @Override + public String getAttribute(final String name) { + final Attribute att = ((org.jdom.Element)shadow).getAttribute(name); + return att == null ? "" : att.getValue(); + } + + @Override + public String getAttributeNS(final String namespaceURI, final String localName) + throws DOMException { + final Attribute att = + ((org.jdom.Element)shadow).getAttribute( + localName, Namespace.getNamespace(namespaceURI)); + return att == null ? "" : att.getValue(); + } + + @Override + public Attr getAttributeNode(final String name) { + return (Attr)getAttributes().getNamedItem(name); + } + + @Override + public Attr getAttributeNodeNS(final String namespaceURI, final String localName) + throws DOMException { + return (Attr)getAttributes().getNamedItemNS(namespaceURI, localName); + } + + /* ******************************************************** + * Illegal modification methods below. + * ******************************************************** */ + + @Override + public Attr setAttributeNodeNS(final Attr newAttr) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void setIdAttribute(final String name, final boolean isId) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void setIdAttributeNS(final String namespaceURI, final String localName, + final boolean isId) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void setIdAttributeNode(final Attr idAttr, final boolean isId) + throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void setAttributeNS(final String namespaceURI, final String qualifiedName, + final String value) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void removeAttributeNS(final String namespaceURI, final String localName) + throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public Attr setAttributeNode(final Attr newAttr) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void setAttribute(final String name, final String value) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public void removeAttribute(final String name) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public Attr removeAttributeNode(final Attr oldAttr) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JEntityRef.java b/contrib/src/java/org/jdom/contrib/dom/JEntityRef.java new file mode 100644 index 0000000..624e910 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JEntityRef.java @@ -0,0 +1,87 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.EntityReference; + +import org.jdom.Content; +import org.jdom.EntityRef; +import org.jdom.Namespace; + +class JEntityRef extends JSimpleContent implements EntityReference { + + public JEntityRef(final JDocument topdoc, final JParent parent, final Content content, + final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNodeName() { + return ((EntityRef)shadow).getName(); + } + + @Override + public String getNodeValue() throws DOMException { + return null; + } + + @Override + public String getTextContent() throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JNamespace.java b/contrib/src/java/org/jdom/contrib/dom/JNamespace.java new file mode 100644 index 0000000..10e27e3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JNamespace.java @@ -0,0 +1,169 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.Attr; +import org.w3c.dom.DOMException; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.TypeInfo; + +import org.jdom.JDOMConstants; +import org.jdom.Namespace; + +final class JNamespace extends JNamespaceAware implements Attr { + + final Namespace ns; + public JNamespace(final JDocument topdoc, final JElement parent, final Namespace namespace, + final Namespace[] nstack) { + super(topdoc, parent, Node.ATTRIBUTE_NODE, nstack); + this.ns = namespace; + } + + @Override + public final Object getWrapped() { + return ns; + } + + @Override + public String getNodeName() { + return getName(); + } + @Override + public String getNodeValue() throws DOMException { + return getValue(); + } + @Override + public NodeList getChildNodes() { + return EMPTYLIST; + } + @Override + public Node getFirstChild() { + return null; + } + @Override + public Node getLastChild() { + return null; + } + @Override + public NamedNodeMap getAttributes() { + return null; + } + @Override + public boolean hasChildNodes() { + return false; + } + @Override + public String getNamespaceURI() { + return "".equals(ns.getPrefix()) ? "" : JDOMConstants.NS_URI_XMLNS; + } + @Override + public String getPrefix() { + return "".equals(ns.getPrefix()) ? "" : JDOMConstants.NS_PREFIX_XMLNS; + } + @Override + public String getLocalName() { + return "".equals(ns.getPrefix()) ? JDOMConstants.NS_PREFIX_XMLNS : ns.getPrefix(); + } + @Override + public boolean hasAttributes() { + return false; + } + @Override + public String getBaseURI() { + return parent.getBaseURI(); + } + @Override + public String getTextContent() throws DOMException { + return ""; + } + @Override + public String getName() { + return "".equals(ns.getPrefix()) ? ns.getPrefix() : (JDOMConstants.NS_PREFIX_XMLNS + ":" + ns.getPrefix()); + } + @Override + public boolean getSpecified() { + return true; + } + @Override + public String getValue() { + return ns.getURI(); + } + + @Override + public Element getOwnerElement() { + return (Element)getParentNode(); + } + + @Override + public TypeInfo getSchemaTypeInfo() { + return TYPEINFO; + } + + @Override + public boolean isId() { + return false; + } + + @Override + public void setValue(final String value) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + +} \ No newline at end of file diff --git a/contrib/src/java/org/jdom/contrib/dom/JNamespaceAware.java b/contrib/src/java/org/jdom/contrib/dom/JNamespaceAware.java new file mode 100644 index 0000000..7f7f117 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JNamespaceAware.java @@ -0,0 +1,100 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.jdom.Namespace; + +abstract class JNamespaceAware extends JNode { + + protected final Namespace[] scope; + + public JNamespaceAware(final JDocument topdoc, final JParent parent, + final short nodetype, final Namespace[] nstack) { + super(topdoc, parent, nodetype); + scope = nstack; + } + + @Override + public final String lookupPrefix(final String namespaceURI) { + for (int i = 0; i < scope.length; i++) { + if (scope[i].getURI().equals(namespaceURI)) { + return scope[i].getPrefix(); + } + } + return null; + } + + @Override + public final boolean isDefaultNamespace(final String namespaceURI) { + for (int i = 0; i < scope.length; i++) { + if (scope[i].getURI().equals(namespaceURI) && "".equals(scope[i].getPrefix())) { + return true; + } + } + return false; + } + + @Override + public final String lookupNamespaceURI(final String prefix) { + for (int i = 0; i < scope.length; i++) { + if (scope[i].getPrefix().equals(prefix)) { + return scope[i].getURI(); + } + } + return null; + } + + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JNode.java b/contrib/src/java/org/jdom/contrib/dom/JNode.java new file mode 100644 index 0000000..78a24a3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JNode.java @@ -0,0 +1,409 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import java.util.ArrayList; +import java.util.HashMap; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.TypeInfo; +import org.w3c.dom.UserDataHandler; + +abstract class JNode implements Node, Wrapper { + + protected static final TypeInfo TYPEINFO = new TypeInfo() { + @Override + public boolean isDerivedFrom(final String typeNamespaceArg, final String typeNameArg, + final int derivationMethod) { + return false; + } + + @Override + public String getTypeNamespace() { + return "http://www.w3.org/TR/REC-xml"; + } + + @Override + public String getTypeName() { + return null; + } + }; + + + + protected static final NodeList EMPTYLIST = new NodeList() { + @Override + public Node item(final int index) { + return null; + } + @Override + public int getLength() { + return 0; + } + }; + + protected static final NamedNodeMap EMPTYMAP = new NamedNodeMap() { + + @Override + public Node setNamedItemNS(final Node arg) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node setNamedItem(final Node arg) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node removeNamedItemNS(final String namespaceURI, final String localName) + throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node removeNamedItem(final String name) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public Node item(final int index) { + return null; + } + + @Override + public Node getNamedItemNS(final String namespaceURI, final String localName) + throws DOMException { + return null; + } + + @Override + public Node getNamedItem(final String name) { + return null; + } + + @Override + public int getLength() { + return 0; + } + }; + + private final short nodetype; + protected final JDocument topdoc; + protected final JParent parent; + + private HashMap userdata; + + JNode(final JDocument topdoc, final JParent parent, final short nodetype) { + // the rule is that only JDocument constructor can pass a null topdoc. + this.topdoc = topdoc == null ? (JDocument)this : topdoc; + this.nodetype = nodetype; + this.parent = parent; + } + + /** + * Attribute, Document, and Element need to override this. + * @param other will be another node of the same type that needs to be + * compared. + * @return true if the rules match the Node.isEqualsNode() method. + */ + protected boolean detailEquals(final JNode other) { + return true; + } + + /* **************************************************** + * Below this all methods are final... + * **************************************************** */ + + @Override + public final Node getPreviousSibling() { + if (parent == null) { + return null; + } + return parent.getPreviousSibling(this); + } + + @Override + public final Node getNextSibling() { + if (parent == null) { + return null; + } + return parent.getNextSibling(this); + } + + + + + @Override + public final Object setUserData(final String key, final Object data, final UserDataHandler handler) { + // since the node can never be adopted or cloned, the handler + // events can never be called, thus we do not need to track the handler. + if (userdata == null) { + userdata = new HashMap(); + } + return userdata.put(key, data); + } + + @Override + public final Object getUserData(final String key) { + if (userdata == null) { + return null; + } + return userdata.get(key); + } + + @Override + public final Object getFeature(final String feature, final String version) { + return null; + } + + @Override + public final Node getParentNode() { + return parent; + } + + @Override + public final short getNodeType() { + return nodetype; + } + + @Override + public final Document getOwnerDocument() { + return topdoc; + } + + @Override + public final short compareDocumentPosition(final Node other) throws DOMException { + if ((other instanceof JNode)) { + JNode their = (JNode)other; + if (their == this) { + return 0; + } + if (their.topdoc == topdoc) { + // same document.... + final ArrayList ancestry = new ArrayList(); + ancestry.add(their); + JNode p = their.parent; + while (p != null) { + if (p == this) { + // we contain the other node. + return DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING; + } + ancestry.add(p); + p = p.parent; + } + // OK, we have the ancestry.... but, we are not on the direct ancestry of that node. + // let's find an intersection of our ancestry... + p = this.parent; + JNode k = this; + final int alen = ancestry.size(); + while (p != null) { + for (int apos = 0; apos < alen; apos++) { + if (p == ancestry.get(apos)) { + // we have intersected... + final int sibpos = apos - 1; + if (sibpos < 0) { + return DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING; + } + final JNode sibling = ancestry.get(sibpos); + // need to get relative order of 'k' and sibling within 'p'. + final JParent prnt = (JParent)p; + if (sibling instanceof JAttribute) { + final NamedNodeMap nnm = prnt.getAttributes(); + for (int i = nnm.getLength() - 1; i >= 0; i--) { + final Node n = nnm.item(i); + if (n == sibling) { + return DOCUMENT_POSITION_FOLLOWING; + } else if (n == k) { + return DOCUMENT_POSITION_PRECEDING; + } + } + } else { + for (int i = prnt.getLength() - 1; i >= 0; i--) { + final Node n = prnt.item(i); + if (n == sibling) { + return DOCUMENT_POSITION_FOLLOWING; + } else if (n == k) { + return DOCUMENT_POSITION_PRECEDING; + } + } + } + throw new IllegalStateException("Sibling nodes appear not to be siblings?"); + } + } + k = p; + p = p.parent; + } + } + return Node.DOCUMENT_POSITION_DISCONNECTED; + } + throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Not same Document Model"); + } + + @Override + public final boolean isEqualNode(final Node other) { + // definition of DOM isEquals: + // Two nodes are equal if and only if the following conditions are satisfied: + // + // The two nodes are of the same type. + // The following string attributes are equal: nodeName, localName, namespaceURI, prefix, nodeValue . This is: they are both null, or they have the same length and are character for character identical. + // The attributes NamedNodeMaps are equal. This is: they are both null, or they have the same length and for each node that exists in one map there is a node that exists in the other map and is equal, although not necessarily at the same index. + // The childNodes NodeLists are equal. This is: they are both null, or they have the same length and contain equal nodes at the same index. Note that normalization can affect equality; to avoid this, nodes should be normalized before being compared. + // + if (other == null) { + return false; + } + if (!getClass().isInstance(other)) { + return false; + } + if (isSameNode(other)) { + return true; + } + if (areSame(getNodeName(), other.getNodeName()) && + areSame(getLocalName(), other.getLocalName()) && + areSame(getNamespaceURI(), other.getNamespaceURI()) && + areSame(getPrefix(), other.getPrefix()) && + areSame(getNodeValue(), other.getNodeValue())) { + return detailEquals((JNode)other); + } + + return false; + } + + @Override + public final boolean isSameNode(final Node other) { + return this == other; + } + + @Override + public final Node cloneNode(final boolean deep) { + // this would modify things.... we skip it. + // as a convenience we just return ourselves ... readonly. + // this breaks the DOM specification, but there is so much + // that is broken by this thin wrapper..... + return this; + } + + @Override + public final void normalize() { + // this would modify things.... we skip it. + } + + @Override + public final boolean isSupported(final String feature, final String version) { + // anything that requires feature support is not supported... ;-) + return false; + } + + @Override + public final void setPrefix(final String prefix) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Node insertBefore(final Node newChild, final Node refChild) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Node replaceChild(final Node newChild, final Node oldChild) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Node removeChild(final Node oldChild) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final Node appendChild(final Node newChild) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final void setTextContent(final String textContent) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + @Override + public final void setNodeValue(final String nodeValue) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + + protected static final boolean areSame(final String a, final String b) { + if (b == null) { + return a == null; + } + if (a == null) { + return false; + } + return a.equals(b); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JNodeList.java b/contrib/src/java/org/jdom/contrib/dom/JNodeList.java new file mode 100644 index 0000000..f06faab --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JNodeList.java @@ -0,0 +1,83 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import java.util.List; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +final class JNodeList implements NodeList { + + private final List list; + + public JNodeList(final List list) { + this.list = list; + } + + @Override + public Node item(final int index) { + if (index < 0 || index >= list.size()) { + return null; + } + return list.get(index); + } + + @Override + public int getLength() { + return list.size(); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JParent.java b/contrib/src/java/org/jdom/contrib/dom/JParent.java new file mode 100644 index 0000000..92da2f6 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JParent.java @@ -0,0 +1,189 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import java.util.List; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; + +abstract class JParent extends JNamespaceAware implements NodeList { + + private JNamespaceAware[] kids = null; + protected final Parent shadow; + + public JParent(final JDocument topdoc, final JParent parent, + final Parent shadow, final short nodetype, + final Namespace[] nstack) { + super(topdoc, parent, nodetype, nstack); + this.shadow = shadow; + } + + @Override + public final Object getWrapped() { + return shadow; + } + + + + private final JNamespaceAware hydrate(final Content k) { + switch(k.getCType()) { + case CDATA: + return topdoc.find((CDATA)k); + case Comment: + return topdoc.find((Comment)k); + case DocType: + return topdoc.find((DocType)k); + case Element: + return topdoc.find((Element)k); + case EntityRef: + return topdoc.find((EntityRef)k); + case ProcessingInstruction: + return topdoc.find((ProcessingInstruction)k); + case Text: + return topdoc.find((Text)k); + } + throw new IllegalStateException("Unexpected content " + k); + } + + protected final JNamespaceAware[] checkKids() { + if (kids != null) { + return kids; + } + + if (shadow == null) { + kids = new JNamespaceAware[0]; + return kids; + } + + final List content = shadow.getContent(); + kids = new JNamespaceAware[content.size()]; + for (int i = 0; i < kids.length; i++) { + kids[i] = hydrate( content.get(i) ); + } + + return kids; + } + + protected final JNode getPreviousSibling(final JNode jNode) { + checkKids(); + for (int i = 0; i < kids.length; i++) { + if (kids[i] == jNode) { + return i > 0 ? kids[i - 1] : null; + } + } + return null; + } + + protected final JNode getNextSibling(final JNode jNode) { + checkKids(); + for (int i = 0; i < kids.length; i++) { + if (kids[i] == jNode) { + return i < kids.length - 1 ? kids[i + 1] : null; + } + } + return null; + } + + @Override + public final boolean hasChildNodes() { + checkKids(); + return kids.length > 0; + } + + + @Override + public final NodeList getChildNodes() { + checkKids(); + return this; + } + + @Override + public final Node getFirstChild() { + checkKids(); + return kids.length > 0 ? kids[0] : null; + } + + @Override + public final Node getLastChild() { + checkKids(); + return kids.length > 0 ? kids[kids.length - 1] : null; + } + + @Override + public final Node item(final int index) { + checkKids(); + return index >= 0 && index < kids.length ? kids[index] : null; + } + + @Override + public final int getLength() { + checkKids(); + return kids.length; + } + + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JProcessingInstruction.java b/contrib/src/java/org/jdom/contrib/dom/JProcessingInstruction.java new file mode 100644 index 0000000..b8b107d --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JProcessingInstruction.java @@ -0,0 +1,102 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.ProcessingInstruction; + +import org.jdom.Content; +import org.jdom.Namespace; + +class JProcessingInstruction extends JSimpleContent implements +ProcessingInstruction { + + public JProcessingInstruction(final JDocument topdoc, final JParent parent, + final Content content, final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNodeName() { + return getTarget(); + } + + @Override + public String getNodeValue() throws DOMException { + return getData(); + } + + @Override + public String getTextContent() throws DOMException { + return getData(); + } + + @Override + public String getTarget() { + return ((org.jdom.ProcessingInstruction)shadow).getTarget(); + } + + @Override + public String getData() { + return ((org.jdom.ProcessingInstruction)shadow).getData(); + } + + @Override + public void setData(final String data) throws DOMException { + throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, + "Cannot modify JDOM Wrapper DOM objects."); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JSimpleCharacterContent.java b/contrib/src/java/org/jdom/contrib/dom/JSimpleCharacterContent.java new file mode 100644 index 0000000..5029d20 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JSimpleCharacterContent.java @@ -0,0 +1,126 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.CharacterData; +import org.w3c.dom.DOMException; + +import org.jdom.Content; +import org.jdom.Namespace; + +abstract class JSimpleCharacterContent extends JSimpleContent implements CharacterData { + + public JSimpleCharacterContent(final JDocument topdoc, final JParent parent, + final Content content, final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public final String getNodeValue() throws DOMException { + return getData(); + } + + @Override + public final String getTextContent() throws DOMException { + return getData(); + } + + @Override + public final String getData() throws DOMException { + return shadow.getValue(); + } + + @Override + public final void setData(final String data) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public final int getLength() { + return getData().length(); + } + + @Override + public final String substringData(final int offset, final int count) throws DOMException { + return getData().substring(offset, offset + count); + } + + @Override + public final void appendData(final String arg) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public final void insertData(final int offset, final String arg) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public final void deleteData(final int offset, final int count) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public final void replaceData(final int offset, final int count, final String arg) + throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JSimpleContent.java b/contrib/src/java/org/jdom/contrib/dom/JSimpleContent.java new file mode 100644 index 0000000..418c8bc --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JSimpleContent.java @@ -0,0 +1,82 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.jdom.Content; +import org.jdom.Namespace; + +abstract class JSimpleContent extends JContent { + + public JSimpleContent(final JDocument topdoc, final JParent parent, + final Content content, final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNamespaceURI() { + return null; + } + + @Override + public String getPrefix() { + return null; + } + + @Override + public String getLocalName() { + return null; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/JText.java b/contrib/src/java/org/jdom/contrib/dom/JText.java new file mode 100644 index 0000000..5c9f34e --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/JText.java @@ -0,0 +1,98 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Text; + +import org.jdom.Content; +import org.jdom.Namespace; +import org.jdom.Verifier; + +class JText extends JSimpleCharacterContent implements Text { + + public JText(final JDocument topdoc, final JParent parent, + final Content content, final Namespace[] nstack) { + super(topdoc, parent, content, nstack); + } + + @Override + public String getNodeName() { + return "#text"; + } + + @Override + public final Text splitText(final int offset) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + + @Override + public final boolean isElementContentWhitespace() { + return Verifier.isAllXMLWhitespace(getData()); + } + + @Override + public final String getWholeText() { + return getData(); + } + + @Override + public final Text replaceWholeText(final String tcontent) throws DOMException { + throw new DOMException( + DOMException.NO_MODIFICATION_ALLOWED_ERR, "JDOM Wrapper"); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/dom/Wrapper.java b/contrib/src/java/org/jdom/contrib/dom/Wrapper.java new file mode 100644 index 0000000..3ce010c --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/Wrapper.java @@ -0,0 +1,84 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dom; + +/** + * All DOM Node instances from this package are also instances of this. + * You can access the JDOM object behind this DOM node by calling the + * getWrapped() method. + * + * @author Rolf Lear + * + */ +public interface Wrapper { + /** + * Get the backing JDOM object. The backing object could be: + *

    + *
  1. null if this is a Document DOM node, and there was not an actual + * JDOM document instance. + *
  2. A JDOM Document for + *
  3. A JDOM Attribute + *
  4. A JDOM Element + *
  5. A JDOM Text + *
  6. A JDOM CDATA + *
  7. A JDOM ProcessingInstruction + *
  8. A JDOM EntityRef + *
  9. A JDOM Comment + *
  10. A JDOM DocTyoe + *
+ * @return the JDOM object that backs this DOM Node. + */ + public Object getWrapped(); +} diff --git a/contrib/src/java/org/jdom/contrib/dom/package.html b/contrib/src/java/org/jdom/contrib/dom/package.html new file mode 100644 index 0000000..a5d55ea --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dom/package.html @@ -0,0 +1,5 @@ + + +Classes enabling the read-only view of a JDOM document as a DOM model. + + diff --git a/contrib/src/java/org/jdom/contrib/dtdaware/AttAwareXMLOutputProcessor.java b/contrib/src/java/org/jdom/contrib/dtdaware/AttAwareXMLOutputProcessor.java new file mode 100644 index 0000000..1ff34c3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dtdaware/AttAwareXMLOutputProcessor.java @@ -0,0 +1,199 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dtdaware; + +import java.io.IOException; +import java.io.Writer; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jdom.Attribute; +import org.jdom.Element; +import org.jdom.output.support.AbstractXMLOutputProcessor; +import org.jdom.output.support.FormatStack; + +/** + * Implement a class that is sensitive to an input document's defaulted/implied + * attributes, and will not output attributes that are implied. + *

+ * You create an instance of this Processor, then tell it what attribute values + * to ignore. You do that by calling the ignore() methods with the details of + * what attributes (and what the required value of that attribute would be) to + * ignore. + *

+ * When the XML is output this processor will ignore those attributes which + * match the 'ignore' specs. + * + * @author Rolf Lear + * + */ +public class AttAwareXMLOutputProcessor extends AbstractXMLOutputProcessor { + /** + * Yeah, this is complicated, but it is basically a search tree index. + * This is a nested index of attribute details: + *

+ *

    + *
  1. at the top is the attribute name + *
  2. then the Attribute value. + *
  3. next is the Element name. We only check this level if the attribute + * name and value matches to something that is ignored. + *
  4. next we check to make sure that we are in the correct Attribute Namespace + *
  5. then finally the Element namespace. + *
+ *

+ * If we search this index tree and all the details match existing entries, + * then it is an ignored attribute. The order is set up in such a way that + * there should not need to be many checks in HashMaps for non-ignored + * content... essentiually we have to have the same attname and attvalue + * before the system even checks the Attribute's parent details. + */ + // attname attval emtname attns emtns + private final Map>>>> + ignores = new HashMap>>>>(); + + /** + * Construct this AttAwareXMLOutputProcessor. + * Add attribute to exclude using the + * {@link #ignore(String, String, String, String, String)} method + */ + public AttAwareXMLOutputProcessor() { + super(); + } + + + /** + * Ignore an attribute (where the attribute and element are both in the + * no-uri namespace). + * @param elementname The name of the Attribute's parent element. + * @param attname The name of the attribute. + * @param attvalue The attribute value to ignore. + */ + public void ignore(String elementname, String attname, String attvalue) { + ignore("", elementname, "", attname, attvalue); + } + + /** + * Ignore an attribute. + * @param elementuri The NamespaceURI of the Attribute's parent Element + * @param elementname The name of the Attribute's parent Element. + * @param atturi The Attribute's namespace URI. + * @param attname The name of the attribute. + * @param attvalue The attribute value. + */ + public void ignore(String elementuri, String elementname, + String atturi, String attname, String attvalue) { + Map>>> av = + ignores.get(attname); + if (av == null) { + av = new HashMap>>>(); + ignores.put(attname, av); + } + Map>> en = av.get(attvalue); + if (en == null) { + en = new HashMap>>(); + av.put(attvalue, en); + } + Map> ans = en.get(elementname); + if (ans == null) { + ans = new HashMap>(); + en.put(elementname, ans); + } + Set ens = ans.get(atturi); + if (ens == null) { + ens = new HashSet(); + ans.put(atturi, ens); + } + ens.add(elementuri); + } + + /** + * This extends the printAttribute code to search for attributes to ignore. + */ + @Override + protected void printAttribute(Writer out, FormatStack fstack, + Attribute attribute) throws IOException { + // do we have anything to ignore. + if (!ignores.isEmpty()) { + // yes, there are ignores. + final Map>>> av = + ignores.get(attribute.getName()); + if (av != null) { + // we are ignoring at least one attribute with this name. + final Map>> en = + av.get(attribute.getValue()); + if (en != null) { + // we ignore something with the attribute name and value + final Element e = attribute.getParent(); + final Map> ans = + en.get(e.getName()); + if (ans != null) { + // and the same Element name + final Set ens = ans.get(attribute.getNamespaceURI()); + if (ens != null && ens.contains(e.getNamespaceURI())) { + // and the same Attribute and Element namespace.... + // we match the ignored content... + // skip this attribute. + return; + } + } + } + } + } + // we are not ignoring this attribute. + super.printAttribute(out, fstack, attribute); + } +} diff --git a/contrib/src/java/org/jdom/contrib/dtdaware/AttFilter.java b/contrib/src/java/org/jdom/contrib/dtdaware/AttFilter.java new file mode 100644 index 0000000..0592fae --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dtdaware/AttFilter.java @@ -0,0 +1,189 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dtdaware; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.jdom.Attribute; +import org.jdom.Element; +import org.jdom.filter2.AbstractFilter; + + +/** + * This AttFilter can be programmed to ignore specific attributes. + * The ignore criteria is based on the Attribute name, value, and the + * Element it is attached to. + * + * @author Rolf Lear + * + */ +public class AttFilter extends AbstractFilter { + + /** + * + */ + private static final long serialVersionUID = 1L; + /** + * Yeah, this is complicated, but it is basically a search tree index. + * This is a nested index of attribute details: + *

+ *

    + *
  1. at the top is the attribute name + *
  2. then the Attribute value. + *
  3. next is the Element name. We only check this level if the attribute + * name and value matches to something that is ignored. + *
  4. next we check to make sure that we are in the correct Attribute Namespace + *
  5. then finally the Element namespace. + *
+ *

+ * If we search this index tree and all the details match existing entries, + * then it is an ignored attribute. The order is set up in such a way that + * there should not need to be many checks in HashMaps for non-ignored + * content... essentiually we have to have the same attname and attvalue + * before the system even checks the Attribute's parent details. + */ + // attname attval emtname attns emtns + private final Map>>>> + ignores = new HashMap>>>>(); + + /** + * Ignore an attribute (where the attribute and element are both in the + * no-uri namespace). + * @param elementname The name of the Attribute's parent element. + * @param attname The name of the attribute. + * @param attvalue The attribute value to ignore. + */ + public void ignore(String elementname, String attname, String attvalue) { + ignore("", elementname, "", attname, attvalue); + } + + /** + * Ignore an attribute. + * @param elementuri The NamespaceURI of the Attribute's parent Element + * @param elementname The name of the Attribute's parent Element. + * @param atturi The Attribute's namespace URI. + * @param attname The name of the attribute. + * @param attvalue The attribute value. + */ + public void ignore(String elementuri, String elementname, + String atturi, String attname, String attvalue) { + Map>>> av = + ignores.get(attname); + if (av == null) { + av = new HashMap>>>(); + ignores.put(attname, av); + } + Map>> en = av.get(attvalue); + if (en == null) { + en = new HashMap>>(); + av.put(attvalue, en); + } + Map> ans = en.get(elementname); + if (ans == null) { + ans = new HashMap>(); + en.put(elementname, ans); + } + Set ens = ans.get(atturi); + if (ens == null) { + ens = new HashSet(); + ans.put(atturi, ens); + } + ens.add(elementuri); + } + + + + + @Override + public Attribute filter(Object content) { + if (!(content instanceof Attribute)) { + return null; + } + Attribute attribute = (Attribute)content; + if (!ignores.isEmpty()) { + // yes, there are ignores. + final Map>>> av = + ignores.get(attribute.getName()); + if (av != null) { + // we are ignoring at least one attribute with this name. + final Map>> en = + av.get(attribute.getValue()); + if (en != null) { + // we ignore something with the attribute name and value + final Element e = attribute.getParent(); + final Map> ans = + en.get(e.getName()); + if (ans != null) { + // and the same Element name + final Set ens = ans.get(attribute.getNamespaceURI()); + if (ens != null && ens.contains(e.getNamespaceURI())) { + // and the same Attribute and Element namespace.... + // we match the ignored content... + // skip this attribute. + return null; + } + } + } + } + } + return attribute; + } + + + +} diff --git a/contrib/src/java/org/jdom/contrib/dtdaware/AttFilteredXMLOutputProcessor.java b/contrib/src/java/org/jdom/contrib/dtdaware/AttFilteredXMLOutputProcessor.java new file mode 100644 index 0000000..f6c5f6a --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/dtdaware/AttFilteredXMLOutputProcessor.java @@ -0,0 +1,101 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.dtdaware; + +import java.io.IOException; +import java.io.Writer; + +import org.jdom.Attribute; +import org.jdom.filter2.Filter; +import org.jdom.output.support.AbstractXMLOutputProcessor; +import org.jdom.output.support.FormatStack; + +/** + * Implement a class that is sensitive to a document's attributes, and will not + * output attributes that fail to match the supplied Filter. + * + * @author Rolf Lear + * + */ +public class AttFilteredXMLOutputProcessor extends AbstractXMLOutputProcessor { + /** + * Attributes that do not match this filter will not be printed. + */ + private final Filter attfilter; + + /** + * Construct this AttAwareXMLOutputProcessor. + * @param attf The filter to use which determines what Attributes are processed. + */ + public AttFilteredXMLOutputProcessor(Filter attf) { + super(); + this.attfilter = attf; + } + + + /** + * This extends the printAttribute code to search for attributes to ignore. + */ + @Override + protected void printAttribute(Writer out, FormatStack fstack, + Attribute attribute) throws IOException { + // do we have anything to ignore. + // we are not ignoring this attribute. + if (!attfilter.matches(attribute)) { + return; + } + super.printAttribute(out, fstack, attribute); + } +} \ No newline at end of file diff --git a/contrib/src/java/org/jdom/contrib/helpers/JDOMHelper.java b/contrib/src/java/org/jdom/contrib/helpers/JDOMHelper.java new file mode 100644 index 0000000..9312b6e --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/helpers/JDOMHelper.java @@ -0,0 +1,119 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.helpers; + +import org.jdom.*; +import java.util.*; + +/**

+ * This class contains static helper methods. + *

+ * @author Alex Rosen + * @deprecated concept has been moved in to core: + * {@link Element#sortContent(Comparator)} + * @see Element#sortChildren(Comparator) + * @see Element#sortContent(Comparator) + * @see Element#sortContent(org.jdom.filter2.Filter, Comparator) + */ +@Deprecated +public class JDOMHelper { + /** + *

+ * Sorts the child elements, using the specified comparator. + * @param parent The parent Element, whose child Elements should be sorted. + * @param c The Comparator to use for ordering the child Elements. + * It will only be given Element objects to compare. + *

+ *

+ * This method overcomes two problems with the standard Collections.sort(): + *

    + *
  • Collections.sort() doesn't bother to remove an item from its old + * location before placing it in its new location, which causes JDOM to + * complain that the item has been added twice. + *
  • This method will sort the child Elements without moving any other + * content, such as formatting text nodes (newlines, indents, etc.) + * Otherwise, all the formatting whitespace would move to the beginning + * or end of the content list. + * (Note that this means that the elements will now be in a different + * order with respect to any comments, which may cause a problem + * if the comments describe the elements.) + *
+ *

+ */ + public static void sortElements(Element parent, Comparator c) { + // Create a new, static list of child elements, and sort it. + List children = new ArrayList(parent.getChildren()); + Collections.sort(children, c); + ListIterator childrenIter = children.listIterator(); + + // Create a new, static list of all content items. + List content = new ArrayList(parent.getContent()); + ListIterator contentIter = content.listIterator(); + + // Loop through the content items, and whenever we find an Element, + // we'll insert the next ordered Element in its place. Because the + // content list is not live, it won't complain about an Element being + // added twice. + while(contentIter.hasNext()) { + Object obj = contentIter.next(); + if (obj instanceof Element) + contentIter.set(childrenIter.next()); + } + + // Finally, we set the content list back into the parent Element. + parent.setContent((List)null); + parent.setContent(content); + } +} \ No newline at end of file diff --git a/contrib/src/java/org/jdom/contrib/helpers/README b/contrib/src/java/org/jdom/contrib/helpers/README new file mode 100644 index 0000000..8f66078 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/helpers/README @@ -0,0 +1,2 @@ +This is an area for little helper classes. It's open to experimentation and +whimsy. diff --git a/contrib/src/java/org/jdom/contrib/helpers/XPathHelper.java b/contrib/src/java/org/jdom/contrib/helpers/XPathHelper.java new file mode 100644 index 0000000..32a75f4 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/helpers/XPathHelper.java @@ -0,0 +1,561 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.helpers; + + +import java.util.Iterator; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.Comment; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter.Filters; + + +/** + * Provides a set of utility methods to generate XPath expressions + * to select a given node in a (subtree of a) document. + *

+ * Note: As this class has no knowledge of the + * document content, the generated XPath expression rely on the + * document structure. Hence any modification of the structure + * of the document may invalidate the generated XPaths.

+ * + * @author Laurent Bihanic + * @deprecated moved in to core: org.jdom.xpath.XPathHelper + * @see org.jdom.xpath.XPathHelper + */ +@Deprecated +public class XPathHelper { + + /** + * Returns the path to the specified Element from the document + * root as an XPath expression. + * + * @param to the Element the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null. + */ + public static String getPathString(Element to) throws JDOMException { + return getPathString(null, to); + } + + /** + * Returns the path from a given JDOM node to the specified + * Element as an XPath expression. + * + * @param from the Document or Element node at which the + * the generated path shall be applied. Use + * null to specify the topmost + * ancestor of the to node. + * @param to the Element the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null or from is not + * a {@link Document} or a {@link Element} node. + */ + public static String getPathString(Object from, Element to) + throws JDOMException { + checkPathStringArguments(from, to); + + if (to == from) { + return "node()"; + } + return getElementPath(from, to, true).toString(); + } + + /** + * Returns the path to the specified Attribute from the document + * root as an XPath expression. + * + * @param to the Attribute the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null. + */ + public static String getPathString(Attribute to) throws JDOMException { + return getPathString(null, to); + } + + /** + * Returns the path from a given JDOM node to the specified + * Attribute as an XPath expression. + * + * @param from the Document or Element node at which the + * the generated path shall be applied. Use + * null to specify the topmost + * ancestor of the to node. + * @param to the Attribute the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null or from is not + * a {@link Document} or a {@link Element} node. + */ + public static String getPathString(Object from, Attribute to) + throws JDOMException { + checkPathStringArguments(from, to); + + if (to == from) { + return "node()"; + } + StringBuilder path = getElementPath(from, to.getParent(), false); + path.append('@').append(to.getName()).toString(); + return path.toString(); + } + + /** + * Returns the path to the specified Text node from the document + * root as an XPath expression. + * + * @param to the Text node the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null. + */ + public static String getPathString(Text to) throws JDOMException { + return getPathString(null, to); + } + + /** + * Returns the path from a given JDOM node to the specified + * Text node as an XPath expression. + * + * @param from the Document or Element node at which the + * the generated path shall be applied. Use + * null to specify the topmost + * ancestor of the to node. + * @param to the Text node the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null or from is not + * a {@link Document} or a {@link Element} node. + */ + public static String getPathString(Object from, Text to) + throws JDOMException { + checkPathStringArguments(from, to); + + if (to == from) { + return "node()"; + } + Element parent = to.getParentElement(); + List siblings = null; + StringBuilder path = getElementPath(from, parent, false); + + if (parent != null) { + siblings = parent.getContent(Filters.text()); + } + else { + Document doc = to.getDocument(); + if (doc != null) { + siblings = doc.getContent(Filters.text()); + } + } + return getPositionPath(to, siblings, "text()", path).toString(); + } + + /** + * Returns the path to the specified Comment from the document + * root as an XPath expression. + * + * @param to the Comment the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null. + */ + public static String getPathString(Comment to) throws JDOMException { + return getPathString(null, to); + } + + /** + * Returns the path from a given JDOM node to the specified + * Comment as an XPath expression. + * + * @param from the Document or Element node at which the + * the generated path shall be applied. Use + * null to specify the topmost + * ancestor of the to node. + * @param to the Comment the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null or from is not + * a {@link Document} or a {@link Element} node. + */ + public static String getPathString(Object from, Comment to) + throws JDOMException { + checkPathStringArguments(from, to); + + if (to == from) { + return "node()"; + } + Element parent = to.getParentElement(); + List siblings = null; + StringBuilder path = getElementPath(from, parent, false); + + if (parent != null) { + siblings = parent.getContent(Filters.comment()); + } + else { + Document doc = to.getDocument(); + if (doc != null) { + siblings = doc.getContent(Filters.comment()); + } + } + return getPositionPath(to, siblings, "comment()", path).toString(); + } + + /** + * Returns the path to the specified ProcessingInstruction node + * from the document root as an XPath expression. + * + * @param to the ProcessingInstruction node the generated path + * shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null. + */ + public static String getPathString(ProcessingInstruction to) + throws JDOMException { + return getPathString(null, to); + } + + /** + * Returns the path from a given JDOM node to the specified + * ProcessingInstruction node as an XPath expression. + * + * @param from the Document or Element node at which the + * the generated path shall be applied. Use + * null to specify the topmost + * ancestor of the to node. + * @param to the ProcessingInstruction node the generated + * path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null or from is not + * a {@link Document} or a {@link Element} node. + */ + public static String getPathString(Object from, ProcessingInstruction to) + throws JDOMException { + checkPathStringArguments(from, to); + + if (to == from) { + return "node()"; + } + Element parent = to.getParentElement(); + List siblings = null; + StringBuilder path = getElementPath(from, parent, false); + + if (parent != null) { + siblings = parent.getContent(Filters.processinginstruction()); + } + else { + Document doc = to.getDocument(); + if (doc != null) { + siblings = doc.getContent(Filters.processinginstruction()); + } + } + return getPositionPath(to, siblings, + "processing-instruction()", path).toString(); + } + + /** + * Returns the path to the specified JDOM node from the document + * root as an XPath expression. + * + * @param to the JDOM node the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if to is + * null or is not a JDOM node selectable + * by XPath expressions (Element, Attribute, Text, + * Comment, ProcessingInstruction). + */ + public static String getPathString(Object to) throws JDOMException { + return getPathString(null, to); + } + + /** + * Returns the path from a JDOM node to another JDOM node + * as an XPath expression. + * + * @param from the Document or Element node at which the + * the generated path shall be applied. Use + * null to specify the topmost + * ancestor of the to node. + * @param to the JDOM node the generated path shall select. + * + * @return an XPath expression to select the specified node. + * + * @throws JDOMException if the XPath generation failed. + * @throws IllegalArgumentException if from is not + * a {@link Document} or a {@link Element} node or + * to is null or is not a JDOM + * node selectable by XPath expressions (Element, + * Attribute, Text, Comment, ProcessingInstruction). + */ + public static String getPathString(Object from, Object to) + throws JDOMException { + if (to instanceof Element) { + return getPathString(from, (Element) to); + } + else if (to instanceof Attribute) { + return getPathString(from, (Attribute) to); + } + else if (to instanceof Text) { + return getPathString(from, (Text) to); + } + else if (to instanceof Comment) { + return getPathString(from, (Comment) to); + } + else if (to instanceof ProcessingInstruction) { + return getPathString(from, (ProcessingInstruction) to); + } + else { + throw new IllegalArgumentException( + "\"to \" shall be an Element, Attribute," + + " Text, Comment or ProcessingInstruction node"); + } + } + + + /** + * Checks that the two arguments of a getPathString() + * call are valid. + * + * @param from the from node. + * @param to the to node. + * + * @throws IllegalArgumentException if one of the arguments is + * invalid. + */ + private static void checkPathStringArguments(Object from, Object to) { + if (!((from == null) || (from instanceof Element) + || (from instanceof Document))) { + throw new IllegalArgumentException("from"); + } + if (to == null) { + throw new IllegalArgumentException("to"); + } + } + + /** + * Returns the XPath expression to select the to + * element relatively to the from element. + * + * @param from the from element. + * @param to the to element. + * @param leaf whether the to is the last element + * of the path to return. + * + * @return an XPath expression to select the to + * element. + * + * @throws JDOMException if the XPath generation failed. + */ + private static StringBuilder getElementPath(Object from, + Element to, boolean leaf) + throws JDOMException { + if (from instanceof Document) { + from = null; + } + return getElementPath((Element) from, to, leaf, new StringBuilder()); + } + + /** + * Returns the XPath expression to select the to + * element relatively to the from element. + * + * @param from the from element. + * @param to the to element. + * @param leaf whether the to is the last element + * of the path to return. + * @param path the buffer to which append the path. + * + * @return an XPath expression to select the to + * element. + * + * @throws JDOMException if the XPath generation failed. + */ + private static StringBuilder getElementPath(Element from, Element to, + boolean leaf, StringBuilder path) + throws JDOMException { + if (to != from) { + List siblings = null; + + Element parent = to.getParentElement(); + if (parent == null) { + // Oops! No more parent but I haven't yet reached the from node. + if (parent != from) { + // Ouch! from node is not an ancestor. + throw new JDOMException( + "The \"from\" node is not an ancestor of the \"to\" node"); + } + if (to.isRootElement()) { + path.append('/'); + } + } + else { + siblings = parent.getChildren(to.getName(), null); + } + + if (parent != from) { + path = getElementPath(from, parent, false, path); + } + + Namespace ns = to.getNamespace(); + if (ns == Namespace.NO_NAMESPACE) { + // No namespace => Use local name only. + path = getPositionPath(to, siblings, to.getName(), path); + } + else { + // Elements belongs to a namespace => Check prefix. + String prefix = to.getNamespacePrefix(); + if ("".equals(prefix)) { + // No prefix (default namespace in scope + // => Use wildcard & local name combination. + path.append("*[local-name()='"). + append(to.getName()).append("']"); + + path = getPositionPath(to, siblings, null, path); + } + else { + // Not the default namespace => Use prefix. + path.append(to.getNamespacePrefix()).append(':'); + + path = getPositionPath(to, siblings, to.getName(), path); + } + } + + if ((!leaf) && (path.length() != 0)) { + path.append('/'); + } + } + return (path); + } + + /** + * Appends the specified path token to the provided buffer + * followed by the position specification of the target node in + * its siblings list. + * + * @param node the target node for the XPath expression. + * @param siblings the siblings of the target node. + * @param pathToken the path token identifying the target node. + * @param buffer the buffer to which appending the XPath + * sub-expression or null if the + * method shall allocate a new buffer. + * + * @return the XPath sub-expression to select the target node + * among its siblings. + */ + private static StringBuilder getPositionPath(Object node, List siblings, + String pathToken, + StringBuilder buffer) { + if (buffer == null) { + buffer = new StringBuilder(); + } + if (pathToken != null) { + buffer.append(pathToken); + } + + if ((siblings != null) && (siblings.size() != 1)) { + int position = 0; + for (Iterator i = siblings.iterator(); i.hasNext();) { + position++; + if (i.next() == node) break; + } + buffer.append('[').append(position).append(']'); + } + return buffer; + } +} + diff --git a/contrib/src/java/org/jdom/contrib/ids/IdAttribute.java b/contrib/src/java/org/jdom/contrib/ids/IdAttribute.java new file mode 100644 index 0000000..43ccf4e --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/IdAttribute.java @@ -0,0 +1,159 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.ids; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.Parent; + + +/** + * A sub-class of the default JDOM Attribute to help + * keeping up-to-date the element lookup table maintained by + * IdDocument. + * + * @author Laurent Bihanic + */ +@SuppressWarnings("javadoc") +public class IdAttribute extends Attribute { + + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + public IdAttribute(String name, String value, Namespace namespace) { + super(name, value, namespace); + } + + public IdAttribute(String name, String value, + AttributeType type, Namespace namespace) { + super(name, value, type, namespace); + } + + public IdAttribute(String name, String value) { + this(name, value, UNDECLARED_TYPE, Namespace.NO_NAMESPACE); + } + + public IdAttribute(String name, String value, AttributeType type) { + this(name, value, type, Namespace.NO_NAMESPACE); + } + + @Override + protected Attribute setParent(Element parent) { + Parent oldParent = this.getParent(); + + super.setParent(parent); + + if (this.getAttributeType() == Attribute.ID_TYPE) { + Document doc; + + // Udpate the owning document's lookup table. + if (oldParent != null) { + doc = oldParent.getDocument(); + if (doc instanceof IdDocument) { + ((IdDocument)doc).removeId(this.getValue()); + } + } + doc = this.getDocument(); + if (doc instanceof IdDocument) { + ((IdDocument)doc).addId(this.getValue(), this.getParent()); + } + } + return this; + } + + @Override + public Attribute setValue(String value) { + String oldValue = this.getValue(); + + super.setValue(value); + + if (this.getAttributeType() == Attribute.ID_TYPE) { + // Udpate the owning document's lookup table. + Document doc = this.getDocument(); + if (doc instanceof IdDocument) { + ((IdDocument)doc).removeId(oldValue); + ((IdDocument)doc).addId(this.getValue(), this.getParent()); + } + } + return this; + } + + @Override + public Attribute setAttributeType(AttributeType type) { + AttributeType oldType = this.getAttributeType(); + + if (type != oldType) { + super.setAttributeType(type); + + // Udpate the owning document's lookup table. + Document doc = this.getDocument(); + if (doc instanceof IdDocument) { + if (oldType == Attribute.ID_TYPE) { + ((IdDocument)doc).removeId(this.getValue()); + } + if (type == Attribute.ID_TYPE) { + ((IdDocument)doc).addId(this.getValue(), this.getParent()); + } + } + } + return this; + } +} + diff --git a/contrib/src/java/org/jdom/contrib/ids/IdDocument.java b/contrib/src/java/org/jdom/contrib/ids/IdDocument.java new file mode 100644 index 0000000..cb5d51e --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/IdDocument.java @@ -0,0 +1,162 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.ids; + +import java.util.*; + +import org.jdom.Document; +import org.jdom.DocType; +import org.jdom.Element; + + +/** + * IdDocument extends the default JDOM document to support looking + * up elements using their ID attribute. + *

+ * Instances of this class should not be created directly but through + * the {@link IdFactory}. Using this factory ensures that the + * document is made of {@link IdElement} instances which update the + * look-up table when the element attribute list is updated.

+ *

+ * The method {@link #getElementById} allows looking up an element + * in the document given the value of its ID attribute. Instead of + * scanning the whole document when searching for an element, + * IdDocument uses a lookup table for fast direct + * access to the elements. Hence, applications using this method + * should see their performances improved compared to walking the + * document tree.

+ * + * @author Laurent Bihanic + */ +@SuppressWarnings("javadoc") +public class IdDocument extends Document { + + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + /** + *

The ID lookup table for the document.

+ */ + private Map ids = new HashMap(); + + /** + *

+ * Creates a new IdDocument, with the supplied + * {@link Element} as the root element and the + * supplied {@link DocType} declaration.

+ * + * @param rootElement Element for document root. + * @param docType DocType declaration. + */ + public IdDocument(Element root, DocType docType) { + super(root, docType); + } + + /** + *

+ * Creates a new Document, with the supplied + * {@link Element} as the root element, and no + * {@link DocType document type} declaration. + *

+ * + * @param rootElement Element for document root. + */ + public IdDocument(Element root) { + super(root); + } + + /** + *

+ * Retrieves an element using the value of its ID attribute as + * key.

+ * + * @param id the value of the ID attribute of the element to + * retrieve. + * @return the Element associated to id + * or null if none was found. + */ + public Element getElementById(String id) { + return ids.get(id); + } + + /** + *

+ * Adds the specified ID to the ID lookup table of this document + * and make it point to the associated element.

+ * + * @param id the ID to add. + * @param elt the Element associated to the ID. + */ + protected void addId(String id, Element elt) { + ids.put(id, elt); + return; + } + + /** + *

+ * Removes the specified ID from the ID lookup table of this + * document.

+ * + * @param id the ID to remove. + * @return true if the ID was found and removed; + * false otherwise. + */ + protected boolean removeId(String id) { + return (ids.remove(id) != null); + } +} + diff --git a/contrib/src/java/org/jdom/contrib/ids/IdElement.java b/contrib/src/java/org/jdom/contrib/ids/IdElement.java new file mode 100644 index 0000000..99da508 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/IdElement.java @@ -0,0 +1,209 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.ids; + +import java.util.*; + +import org.jdom.Attribute; +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Parent; +import org.jdom.Namespace; + +/** + * A sub-class of the default JDOM Element to help + * keeping up-to-date the element lookup table maintained by + * IdDocument. + * + * @author Laurent Bihanic + */ +@SuppressWarnings("javadoc") +public class IdElement extends Element { + + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + // Allow Javadocs to inherit from superclass + + public IdElement(String name, Namespace namespace) { + super(name, namespace); + } + + public IdElement(String name) { + this(name, (Namespace)null); + } + + public IdElement(String name, String uri) { + this(name, Namespace.getNamespace("", uri)); + } + + public IdElement(String name, String prefix, String uri) { + this(name, Namespace.getNamespace(prefix, uri)); + } + + @Override +protected Content setParent(Parent parent) { + // Save previous owning document (if any). + Document prevDoc = this.getDocument(); + Document newDoc = (parent != null)? parent.getDocument(): null; + + // Attach to new parent element. + super.setParent(parent); + + if (newDoc != prevDoc) { + // New and previous owning documents are different. + // => Remove all the IDs for the subtree this element is the root + // of from the previous owning document's lookup table and + // insert them into the new owning document's lookup table. + transferIds(prevDoc, newDoc); + } + return this; + } + + + /** + *

+ * Transfers all the IDs for the subtree this element is the root + * of from the lookup table of the previous owning document to + * the lookup table of the new owning document.

+ *

+ * If either of the documents is null or is not an + * {@link IdDocument}, this method performs not action on the + * document. Hence, this method supports being called with both + * documents equal to null.

+ * + * @param prevDoc the previous owning document. + * @param newDoc the new owning document. + */ + private void transferIds(Document prevDoc, Document newDoc) + { + if ((prevDoc instanceof IdDocument) || (newDoc instanceof IdDocument)) { + // At least one of the documents supports lookup by ID. + // => Walk subtree to collect ID mappings. + Map ids = getIds(this, new HashMap()); + + // Update previous owning document. + if (prevDoc instanceof IdDocument) { + // Document supports lookup by ID. + // => Remove IDs from document's lookup table. + IdDocument idDoc = (IdDocument)prevDoc; + + for (String key : ids.keySet()) { + idDoc.removeId(key); + } + } + // Else: Lookup by ID not supported. => Nothing to update! + + // Update new owning document. + if (newDoc instanceof IdDocument) { + // Document supports lookup by ID. + // => Add IDs from document's lookup table. + IdDocument idDoc = (IdDocument)newDoc; + + for (Map.Entry me : ids.entrySet()) { + idDoc.addId(me.getKey(), me.getValue()); + } + } + // Else: Lookup by ID not supported. => Nothing to update! + } + // Else: None of the documents supports lookup by ID not supported. + // => Ignore call. + return; + } + + /** + *

+ * Retrieves the ID attributes for the subtree rooted at + * root to populate the ID mapping table + * ids. In the mapping table, ID attribute values are + * the keys to access the elements.

+ * + * @param root the root element of the subtree to walk. + * @param ids the ID lookup table to populate. + * + * @return the updated ID lookup table. + */ + private static Map getIds(Element root, Map ids) { + addIdAttributes(root, ids); + + for (Element emt : root.getChildren()) { + getIds(emt, ids); + } + return ids; + } + + /** + *

+ * Gets the ID attribute for elt and adds the + * correpsonding entries in the ID mapping table + * ids.

+ * + * @param elt the element to the ID attributes from. + * @param ids the ID lookup table to populate. + */ + private static void addIdAttributes(Element elt, Map ids) { + for (Attribute attr : elt.getAttributes()) { + if (attr.getAttributeType() == Attribute.ID_TYPE) { + ids.put(attr.getValue(), elt); + break; + } + } + return; + } +} + diff --git a/contrib/src/java/org/jdom/contrib/ids/IdFactory.java b/contrib/src/java/org/jdom/contrib/ids/IdFactory.java new file mode 100644 index 0000000..2467b8f --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/IdFactory.java @@ -0,0 +1,152 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.ids; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.Document; +import org.jdom.DocType; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.DefaultJDOMFactory; + +/** + * The IdFactory extends the default JDOM factory to + * build documents that support looking up elements using their ID + * attribute. + *

+ * Looking-up elements by ID only works if a DTD is associated to + * the XML document as the information defining some attributes as + * IDs is only available from the DTD and not from the XML document + * itself.

+ *

+ * The Documents created by this factory are instances of + * {@link IdDocument} which provides the method + * {@link IdDocument#getElementById} to look up an element given the + * value of its ID attribute.

+ *

+ * The following code snippet demonstrates how to use the + * IdFactory with JDOM's SAXBuilder to create an + * IdDocument.

+ *
+ *  SAXBuilder builder = new SAXBuilder();
+ *  builder.setFactory(new IdFactory());
+ *
+ *  IdDocument doc = (IdDocument)(builder.build(xmlDocument));
+ *
+ *  Element elt = doc.getElementById(idValue);
+ * 
+ * + * @author Laurent Bihanic + */ +public class IdFactory extends DefaultJDOMFactory { + + /** + * Creates a new IdFactory object. + */ + public IdFactory() { + super(); + } + + // Allow Javadocs to inherit from superclass + + @Override + public Attribute attribute(String name, String value, Namespace namespace) { + return new IdAttribute(name, value, namespace); + } + + @Override + public Attribute attribute(String name, String value, + AttributeType type, Namespace namespace) { + return new IdAttribute(name, value, type, namespace); + } + + @Override + public Attribute attribute(String name, String value) { + return new IdAttribute(name, value); + } + + @Override + public Attribute attribute(String name, String value, AttributeType type) { + return new IdAttribute(name, value, type); + } + + @Override + public Document document(Element rootElement, DocType docType) { + return new IdDocument(rootElement, docType); + } + @Override + public Document document(Element rootElement) { + return new IdDocument(rootElement); + } + + @Override + public Element element(final int line, final int col, String name, Namespace namespace) { + return new IdElement(name, namespace); + } + @Override + public Element element(final int line, final int col, String name) { + return new IdElement(name); + } + @Override + public Element element(final int line, final int col, String name, String uri) { + return new IdElement(name, uri); + } + @Override + public Element element(final int line, final int col, String name, String prefix, String uri) { + return new IdElement(name, prefix, uri); + } +} + diff --git a/contrib/src/java/org/jdom/contrib/ids/README.txt b/contrib/src/java/org/jdom/contrib/ids/README.txt new file mode 100644 index 0000000..85ba16b --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/README.txt @@ -0,0 +1,26 @@ + +This package demonstrates how to use the attribute type support provided +by JDOM to create JDOM documents that allow looking up elements using +the value of their ID attribute. +Note that for an attribute to be recognized as an ID, the XML document +must be associated to a DTD which defines the type of the attributes. + +For detailed information, please refer to the package Javadoc documentation +or the file "package.html" in this directory. + + +The "doc-files" directory contains simple test cases that demonstrate how +to use the IdFactory to build an IdDocument and how to retrieve an element +by its ID from an IdDocument: + + - TestIds.java is a simple program that builds an IdDocument from the + filename passed as first argument and looks up the element whose ID + value matches the second argument. + Usage: java TestIds + + - testIds.xml is an example of XML file that can be used with the above + sample. It is associated to the DTD "testIds.dtd" which defines which + attributes are IDs. + + +-- Laurent Bihanic diff --git a/contrib/src/java/org/jdom/contrib/ids/doc-files/TestIds.java b/contrib/src/java/org/jdom/contrib/ids/doc-files/TestIds.java new file mode 100644 index 0000000..b5748d0 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/doc-files/TestIds.java @@ -0,0 +1,90 @@ +/*-- + + Copyright (C) 2002-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + +import org.jdom.*; +import org.jdom.input.SAXBuilder; +import org.jdom.output.*; + +import org.jdom.contrib.ids.IdDocument; +import org.jdom.contrib.ids.IdFactory; + + +@SuppressWarnings("javadoc") +public class TestIds { + + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.out.println("Usage: java TestIds "); + System.exit(2); + } + + SAXBuilder builder = new SAXBuilder(); + builder.setJDOMFactory(new IdFactory()); + + IdDocument doc = (IdDocument)(builder.build(args[0])); + Element elt = doc.getElementById(args[1]); + + if (elt != null) { + new XMLOutputter2(Format.getPrettyFormat()).output(elt, System.out); + System.out.println(); + System.exit(0); + } + else { + System.out.println("No element with ID \"" + args[1] + "\" found"); + System.exit(1); + } + } +} + diff --git a/contrib/src/java/org/jdom/contrib/ids/doc-files/testIds.dtd b/contrib/src/java/org/jdom/contrib/ids/doc-files/testIds.dtd new file mode 100644 index 0000000..0019fec --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/doc-files/testIds.dtd @@ -0,0 +1,14 @@ + + + + + + diff --git a/contrib/src/java/org/jdom/contrib/ids/doc-files/testIds.xml b/contrib/src/java/org/jdom/contrib/ids/doc-files/testIds.xml new file mode 100644 index 0000000..fabaa84 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/doc-files/testIds.xml @@ -0,0 +1,35 @@ + + + +]> + + + entry A + entry B + entry C + entry D + entry E + entry F + entry G + entry H + entry I + entry J + entry K + entry L + entry M + entry N + entry O + entry P + entry Q + entry R + entry S + entry T + entry U + entry V + entry W + entry X + entry Y + entry Z + + diff --git a/contrib/src/java/org/jdom/contrib/ids/package.html b/contrib/src/java/org/jdom/contrib/ids/package.html new file mode 100644 index 0000000..2918a33 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/ids/package.html @@ -0,0 +1,20 @@ + + + Provides support for Documents allowing looking up elements using + the value of their ID attribute. +

+ ID attributes are define in DTDs. Hence, the lookup features + provided by this package are available only for XML documents + associated to a DTD and only for the elements for which the DTD + defines an ID attribute.

+

+ Please refer to IdFactory for details + on how to use IdFactory within an application.

+

+ A sample application is provided + here, with an example + XML file and its + DTD.

+ + + diff --git a/contrib/src/java/org/jdom/contrib/input/LineNumberElement.java b/contrib/src/java/org/jdom/contrib/input/LineNumberElement.java new file mode 100644 index 0000000..a034343 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/LineNumberElement.java @@ -0,0 +1,126 @@ +/*-- + + Copyright (C) 2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + package org.jdom.contrib.input; + +import org.jdom.Element; +import org.jdom.Namespace; + +/** + * This class extends a normal Element with a traceback to its + * beginning and endling line number, if available and reported. + *

+ * Each instance is created using a factory internal to the + * LineNumberSAXBuilder class. + * + * @author Per Norrman + * + */ +@SuppressWarnings("javadoc") +public class LineNumberElement extends Element +{ + /** + * Default. + */ + private static final long serialVersionUID = 1L; + + private int _startLine; + private int _endLine; + + public LineNumberElement(String name) { + super(name); + } + + public LineNumberElement() + { + super(); + } + + public LineNumberElement(String name, String uri) + { + super(name, uri); + } + + public LineNumberElement(String name, String prefix, String uri) + { + super(name, prefix, uri); + } + + public LineNumberElement(String name, Namespace namespace) { + super(name, namespace); + } + + + + public int getEndLine() + { + return _endLine; + } + + public int getStartLine() + { + return _startLine; + } + + public void setEndLine(int i) + { + _endLine = i; + } + + public void setStartLine(int i) + { + _startLine = i; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/input/LineNumberSAXHandler.java b/contrib/src/java/org/jdom/contrib/input/LineNumberSAXHandler.java new file mode 100644 index 0000000..616a434 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/LineNumberSAXHandler.java @@ -0,0 +1,167 @@ +/*-- + + Copyright (C) 2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.input; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; + +import org.jdom.DefaultJDOMFactory; +import org.jdom.Element; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.input.sax.SAXHandler; +import org.jdom.input.sax.SAXHandlerFactory; + +/** + * This builder works in parallell with {@link LineNumberElement} + * to provide each element with information on its beginning and + * ending line number in the corresponding source. + * This only works for SAX parsers that supply that information, and + * since this is optional, there are no guarantees. + *

+ * Note that this builder always creates its own for each + * build, thereby cancelling any previous call to setFactory. + *

+ * All elements created are instances of {@link LineNumberElement}. + * No other construct currently receive line number information. + * + * @author Per Norrman + * + */ +public class LineNumberSAXHandler extends SAXHandler { + + /** + * A SAXHandlerFactory that can be used to supply LineNumberSAXHandler + * instances to SAXBuilder. + */ + public static final SAXHandlerFactory SAXFACTORY = new SAXHandlerFactory() { + @Override + public SAXHandler createSAXHandler(JDOMFactory factory) { + // ignore input factory, we use our own. + return new LineNumberSAXHandler(); + } + }; + + private static class MyJDOMFactory extends DefaultJDOMFactory { + + @Override + public Element element(final int line, final int col, String name) + { + return new LineNumberElement(name); + } + + @Override + public Element element(final int line, final int col, String name, String prefix, String uri) + { + return new LineNumberElement(name, prefix, uri); + } + + @Override + public Element element(final int line, final int col, String name, Namespace namespace) + { + return new LineNumberElement(name, namespace); + } + + @Override + public Element element(final int line, final int col, String name, String uri) + { + return new LineNumberElement(name, uri); + } + + } + + /** + * Create a new instance of the LineNumberSAXHandler. + */ + public LineNumberSAXHandler() + { + super(new MyJDOMFactory()); + } + + /** override */ + @Override + public void startElement( + String arg0, + String arg1, + String arg2, + Attributes arg3) + throws SAXException + { + super.startElement(arg0, arg1, arg2, arg3); + Locator l = getDocumentLocator(); + if (l != null) + { + ((LineNumberElement) getCurrentElement()).setStartLine( + l.getLineNumber()); + } + } + + /** override */ + @Override + public void endElement(String arg0, String arg1, String arg2) + throws SAXException + { + Locator l = getDocumentLocator(); + if (l != null) + { + ((LineNumberElement) getCurrentElement()).setEndLine( + l.getLineNumber()); + } + + super.endElement(arg0, arg1, arg2); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/input/README b/contrib/src/java/org/jdom/contrib/input/README new file mode 100644 index 0000000..149e8fb --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/README @@ -0,0 +1 @@ +Location for development of experimental JDOM builders. diff --git a/contrib/src/java/org/jdom/contrib/input/ResultSetBuilder.java b/contrib/src/java/org/jdom/contrib/input/ResultSetBuilder.java new file mode 100644 index 0000000..105b0d3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/ResultSetBuilder.java @@ -0,0 +1,484 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.input; + +import java.sql.*; +import java.text.*; +import java.util.*; + +import org.jdom.*; + +/** + *

ResultSetBuilder builds a JDOM tree from a + * java.sql.ResultSet. Many good ideas were leveraged from + * SQLBuilder written from Jon Baer.

+ * + * Notes: + * Uses name returned by rsmd.getColumnName(), not getColumnLabel() + * because that is less likely to be a valid XML element title. + * Null values are given empty bodies, but you can mark them as empty with + * an attribute using the setNullAttribute() method. Be aware that databases + * may change the case of column names. setAsXXX() methods are case + * insensitive on input column name. Assign each one a proper output name + * if you're worried. Only build() throws JDOMException. Any exceptions + * encountered in the set methods are thrown during the build(). + * The setAsXXX(String columnName, ...) methods do not verify that a column + * with the given name actually exists. + * + * Still needs method-by-method Javadocs. + *

+ * Issues: + * Do attributes have to be added in a namespace? + * + * @author Jason Hunter + * @author Jon Baer + * @author David Bartle + * @author Robert J. Munro + * @version 0.5 + */ +@SuppressWarnings("javadoc") +public class ResultSetBuilder { + + /** The ResultSet that becomes a Document */ + private ResultSet rs; + + /** The meta data from the ResultSet */ + private ResultSetMetaData rsmd; + + /** Allows for throwing an exception whenever needed if caught early on */ + private SQLException exception; + + /** Map of original column names to display names */ + private Map names = new HashMap(); + + /** + * Maps column data to be located either as an Attribute of + * the row (if in the Map) or a child Element of the row + * (if not in the Map) + */ + private Map attribs = new HashMap(); + + /** The Namespace to use for each Element */ + private Namespace ns = Namespace.NO_NAMESPACE; + + /** The maximum rows to return from the result set */ + int maxRows = Integer.MAX_VALUE; // default to all + + /** Name for the root Element of the Document */ + private String rootName = "result"; + + /** Name for the each immediate child Element of the root */ + private String rowName = "entry"; + + /** Name for attribute to mark that a field was null */ + private String nullAttribName = null; + + /** Value for attribute to mark that a field was null */ + private String nullAttribValue = null; + + /** + *

+ * This sets up a java.sql.ResultSet to be built + * as a Document. + *

+ * + * @param rs java.sql.ResultSet to build + */ + public ResultSetBuilder(ResultSet rs) { + this.rs = rs; + try { + rsmd = rs.getMetaData(); + } + catch (SQLException e) { + // Hold the exception until build() is called + exception = e; + } + } + + /** + *

+ * This sets up a java.sql.ResultSet to be built + * as a Document. + *

+ * + * @param rs java.sql.ResultSet to build from + * @param rootName String name for the root + * Element + * of the Document + * @param rowName String name for the each immediate child + * Element of the root + */ + public ResultSetBuilder(ResultSet rs, String rootName, String rowName) { + this(rs); + setRootName(rootName); + setRowName(rowName); + } + + /** + *

+ * This sets up a java.sql.ResultSet to be built + * as a Document. + *

+ * + * @param rs java.sql.ResultSet to build from + * @param rootName String name for the root + * Element + * of the Document + * @param rowName String name for the each immediate child + * Element of the root + * @param ns Namespace to use for each Element + */ + public ResultSetBuilder(ResultSet rs, + String rootName, String rowName, Namespace ns) { + this(rs, rootName, rowName); + setNamespace(ns); + } + + /** + *

+ * This builds a Document from the + * java.sql.ResultSet. + *

+ * + * @return Document - resultant Document object. + * @throws JDOMException when there is a problem + * with the build. + * + */ + public Document build() throws JDOMException { + if (exception != null) { + throw new JDOMException("Database problem", exception); + } + + try { + int colCount = rsmd.getColumnCount(); + + Element root = new Element(rootName, ns); + Document doc = new Document(root); + + int rowCount = 0; + + // get the column labels for this record set + String[] columnName = new String[colCount]; + for (int index = 0; index < colCount; index++) { + columnName[index] = rsmd.getColumnName(index+1); + } + + // build the org.jdom.Document out of the result set + String name; + String value; + Element entry; + Element child; + + while (rs.next() && (rowCount++ < maxRows)) { + entry = new Element(rowName, ns); + for (int col = 1; col <= colCount; col++) { + if (names.isEmpty()) { + name = columnName[col-1]; + } + else { + name = lookupName(columnName[col-1]); + } + + value = getString(rs, col, rsmd.getColumnType(col)); + if (!attribs.isEmpty() && isAttribute(columnName[col-1])) { + if (!rs.wasNull()) { + entry.setAttribute(name, value); + } + } + else { + child = new Element(name, ns); + if (!rs.wasNull()) { + child.setText(value); + } else { + if (nullAttribName != null) { + child.setAttribute(nullAttribName, nullAttribValue); + } + } + entry.addContent(child); + } + } + root.addContent(entry); + } + + return doc; + } + catch (SQLException e) { + throw new JDOMException("Database problem", e); + } + } + + protected String getString(ResultSet prs, int column, int columnType) + throws SQLException { + if (columnType == Types.TIMESTAMP) { + Timestamp timeStamp = prs.getTimestamp(column); + if (timeStamp != null) { + return DateFormat.getDateTimeInstance(DateFormat.FULL, + DateFormat.FULL).format(timeStamp); + } + } + if (columnType == Types.DATE) { + java.sql.Date date = prs.getDate(column); + if (date != null) { + return DateFormat.getDateInstance(DateFormat.FULL).format(date); + } + } + if (columnType == Types.TIME) { + java.sql.Time time = prs.getTime(column); + if (time != null) { + return DateFormat.getTimeInstance(DateFormat.FULL).format(time); + } + } + return prs.getString(column); + } + + private String lookupName(String origName) { + String name = names.get(origName.toLowerCase()); + if (name != null) { + return name; + } + return origName; + } + + private boolean isAttribute(String origName) { + Boolean val = attribs.get(origName.toLowerCase()); + if (val == Boolean.TRUE) { + return true; + } + return false; + } + + /** + * Set the name to use as the root element in + * the Document. + * + * @param rootName String the new name. + * + */ + public void setRootName(String rootName) { + this.rootName = rootName; + } + + /** + * Set the name to use as the row element in + * the Document. + * + * @param rowName String the new name. + * + */ + public void setRowName(String rowName) { + this.rowName = rowName; + } + + /** + *

+ * Set the Namespace to use for + * each Element in the Document. + *

+ * + * @param ns String the namespace to use. + * + */ + public void setNamespace(Namespace ns) { + this.ns = ns; + } + + /** + *

+ * Set the maximum number of rows to add to your + * Document. + *

+ * + * @param maxRows int + * + */ + public void setMaxRows(int maxRows) { + this.maxRows = maxRows; + } + + /** + *

+ * Set a column as an Attribute of a row using the + * original column name. The attribute will appear as the original + * column name. + *

+ * + * @param columnName String the original column name + * + */ + public void setAsAttribute(String columnName) { + attribs.put(columnName.toLowerCase(), Boolean.TRUE); + } + + /** + *

+ * Set a column as an Attribute of a row using the + * column name. The attribute will appear as the new name provided. + *

+ * + * @param columnName String original column name + * @param attribName String new name to use for the attribute + * + */ + public void setAsAttribute(String columnName, String attribName) { + attribs.put(columnName.toLowerCase(), Boolean.TRUE); + names.put(columnName.toLowerCase(), attribName); + } + + /** + *

+ * Set a column as an Attribute of a row using the + * column number. The attribute will appear as the original column + * name. + *

+ * + * @param columnNum int + * + */ + public void setAsAttribute(int columnNum) { + try { + String name = rsmd.getColumnName(columnNum).toLowerCase(); + attribs.put(name, Boolean.TRUE); + } + catch (SQLException e) { + exception = e; + } + } + + /** + *

+ * Set a column as an Attribute of a row using the + * column number. The attribute will appear as new name provided. + *

+ * + * @param columnNum int + * @param attribName String new name to use for the attribute + * + */ + public void setAsAttribute(int columnNum, String attribName) { + try { + String name = rsmd.getColumnName(columnNum).toLowerCase(); + attribs.put(name, Boolean.TRUE); + names.put(name, attribName); + } + catch (SQLException e) { + exception = e; + } + } + + /** + *

+ * Set a column as an Element of a row using the + * column name. The element name will appear as the new name provided. + *

+ * + * @param columnName String original column name + * @param elemName String new name to use for the element + * + */ + public void setAsElement(String columnName, String elemName) { + String name = columnName.toLowerCase(); + attribs.put(name, Boolean.FALSE); + names.put(name, elemName); + } + + /** + *

+ * Set a column as an Element of a row using the + * column number. The element name will appear as new name provided. + *

+ * + * @param columnNum int + * @param elemName String new name to use for the element + * + */ + public void setAsElement(int columnNum, String elemName) { + try { + String name = rsmd.getColumnName(columnNum).toLowerCase(); + attribs.put(name, Boolean.FALSE); + names.put(name, elemName); + } + catch (SQLException e) { + exception = e; + } + } + + /** + *

+ * Set a specific attribute to use to mark that a value in the + * database was null, not just an empty string. This is necessary + * because <foo/> semantically represents both null and empty. + * This method lets you have <foo null="true">. + *

+ * + * @param nullAttribName String name of attribute to add + * @param nullAttribValue String value to set it to. + * + */ + public void setNullAttribute(String nullAttribName, + String nullAttribValue) { + this.nullAttribName = nullAttribName; + this.nullAttribValue = nullAttribValue; + } + +/* + public void setAsIngore(String columnName) { + } + + public void setAsIngore(int columnNum) { + } +*/ + +} diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/ElementListener.java b/contrib/src/java/org/jdom/contrib/input/scanner/ElementListener.java new file mode 100644 index 0000000..a8b4b5c --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/ElementListener.java @@ -0,0 +1,96 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.input.scanner; + +import org.jdom.Element; +import org.jdom.JDOMException; + +/** + * The interface objects listening for element creation notification + * fired by {@link ElementScanner} shall implement. + * + * @author Laurent Bihanic + */ +public interface ElementListener { + + /** + * Notifies of the parsing of an Element. + *

+ * ElementScanner invokes this method when + * encountering the closing tag of the element definition. Thus, + * element e and all its child nodes are fully built + * but the parent element is not.

+ *

+ * Note that the element is not attached to any + * {@link Element#getDocument document} and that the + * {@link Element#getParent() parent element} is null + * unless another listener is listening on one of the element + * ancestors.

+ *

+ * As no copy of the notified elements is performed, all changes + * made on element e will be visible of the + * not-yet-notified listeners listening on this same element and + * of the listeners listening on one of the element ancestors.

+ * + * @param path the path to the parsed element. + * @param e the parsed Element. + * + * @throws JDOMException if the listener wishes to abort the + * parsing of the input XML document. + */ + abstract public void elementMatched(String path, Element e) + throws JDOMException; +} + diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/ElementScanner.java b/contrib/src/java/org/jdom/contrib/input/scanner/ElementScanner.java new file mode 100644 index 0000000..508aa84 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/ElementScanner.java @@ -0,0 +1,772 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.input.scanner; + +import java.io.IOException; +import java.util.*; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.helpers.XMLFilterImpl; + +import org.jdom.*; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaders; +import org.jdom.input.sax.XMLReaderJDOMFactory; +import org.jdom.input.sax.SAXHandler; +import org.jdom.input.sax.SAXHandlerFactory; + +/** + * An XML filter that uses XPath-like expressions to select the + * element nodes to build and notifies listeners when these + * elements becomes available during the parse. + *

+ * ElementScanner does not aim at providing a faster parsing of XML + * documents. Its primary focus is to allow the application to + * control the parse and to consume the XML data while they are + * being parsed. ElementScanner can be viewed as a high-level SAX + * parser that fires events conveying JDOM {@link Element elements} + * rather that XML tags and character data.

+ *

+ * ElementScanner only notifies of the parsing of element nodes and + * does not support reporting the parsing of DOCTYPE data, processing + * instructions or comments except for those present within the + * selected elements. Application needing such data shall register + * a specific {@link ContentHandler} of this filter to receive them + * in the form of raw SAX events.

+ *

+ * To be notified of the parsing of JDOM Elements, an application + * shall {@link #addElementListener register} objects implementing + * the {@link ElementListener} interface. For each registration, + * an XPath-like expression defines the elements to be parsed and + * reported.

+ *

+ * Opposite to XPath, there is no concept of current context + * or current node in ElementScanner. And thus, the syntax + * of the "XPath-like expressions" is not as strict as in XPath and + * closer to what one uses in XSLT stylesheets in the + * match specification of the XSL templates:
+ * In ElementScanner, the expression "x" matches any + * element named "x" at any level of the document and not + * only the root element (as expected in strict XPath if the + * document is considered the current context). Thus, in + * ElementScanner, "x" is equivalent to + * "//x".

+ *

+ * Example: + *

+ *  ElementScanner f = new ElementScanner();
+ *
+ *  // All descendants of x named y
+ *  f.addElementListener(new MyImpl(), "x//y");
+ *  // All grandchilden of y named t
+ *  f.addElementListener(new MyImpl(), "y/* /t");
+ *
+ *  ElementListener l2 = new MyImpl2();
+ *  f.addElementListener(l2, "/*");     // Root element
+ *  f.addElementListener(l2, "z");      // Any node named z
+ *
+ *  ElementListener l3 = new MyImpl3();
+ *  // Any node having an attribute "name" whose value contains ".1"
+ *  f.addElementListener(l3, "*[contains(@name,'.1')]");
+ *  // Any node named y having at least one "y" descendant
+ *  f.addElementListener(l3, "y[.//y]");
+ *
+ *  f.parse(new InputSource("test.xml"));
+ *  
+ *

+ *

+ * The XPath interpreter can be changed (see {@link XPathMatcher}). + * The default implementation is a mix of the + * Jakarta + * RegExp package and the + * Jaxen XPath interpreter.

+ *

+ * ElementScanner splits XPath expressions in 2 parts: a node + * selection pattern and an optional test expression (the part of + * the XPath between square backets that follow the node selection + * pattern).

+ *

+ * Regular expressions are used to match nodes applying the node + * selection pattern. This allows matching node without requiring to + * build them (as Jaxen does).
+ * If a test expression appears in an XPath expression, Jaxen is used + * to match the built elements against it and filter out those not + * matching the test.

+ *

+ * As a consequence of using regular expressions, the or" + * operator ("|" in XPath) is not supported in node + * selection patterns but can be achieved by registering the same + * listener several times with different node patterns.

+ *

+ * Note: The methods marked with + * "[ContentHandler interface support]" below shall not be + * invoked by the application. Their usage is reserved to + * the XML parser.

+ * + * @author Laurent Bihanic + */ +@SuppressWarnings("javadoc") +public class ElementScanner extends XMLFilterImpl { + + /** + * The registered element listeners, each wrapped in a + * XPathMatcher instance. + */ + private final Collection listeners = new ArrayList(); + + /** + * The SAXBuilder instance to build the JDOM objects used + * for parsing the input XML documents. We actually do not need + * SAXBuilder per se, we just want to reuse the tons of Java code + * this class implements! + */ + private SAXBuilder parserBuilder = new SAXBuilder(); + + /** + * The SAXHandler instance to build the JDOM Elements. + */ + private SAXHandler saxHandler = null; + + /** + * The path of the being parsed element. + */ + private StringBuilder currentPath = new StringBuilder(); + + /** + * The matching rules active for the current path. It includes + * the matching rules active for all the ancestors of the + * current node. + */ + private Map> activeRules = new HashMap>(); + + /** + * Construct an ElementScanner, with no parent. + *

+ * If no parent has been assigned when {@link #parse} is invoked, + * ElementScanner will use JAXP to get an instance of the default + * SAX parser installed.

+ */ + public ElementScanner() { + super(); + } + + /** + * Constructs an ElementScanner with the specified parent. + */ + public ElementScanner(XMLReader parent) { + super(parent); + } + + //------------------------------------------------------------------------- + // Specific implementation + //------------------------------------------------------------------------- + + /** + * Adds a new element listener to the list of listeners + * maintained by this filter. + *

+ * The same listener can be registered several times using + * different patterns and several listeners can be registered + * using the same pattern.

+ * + * @param listener the element listener to add. + * @param pattern the XPath expression to select the elements + * the listener is interested in. + * + * @throws JDOMException if listener is null or + * the expression is invalid. + */ + public void addElementListener(ElementListener listener, String pattern) + throws JDOMException { + if (listener != null) { + this.listeners.add(XPathMatcher.newXPathMatcher(pattern, listener)); + } + else { + throw (new JDOMException("Invalid listener object: ")); + } + } + + /** + * Removes element listeners from the list of listeners maintained + * by this filter. + *

+ * if pattern is null, this method + * removes all registrations of listener, regardless + * the pattern(s) used for creating the registrations.

+ *

+ * if listener is null, this method + * removes all listeners registered for pattern.

+ *

+ * if both listener and pattern are + * null, this method performs no action!

+ * + * @param listener the element listener to remove. + */ + public void removeElementListener(ElementListener listener, String pattern) { + if ((listener != null) || (pattern != null)) { + for (Iterator i=this.listeners.iterator(); i.hasNext(); ) { + XPathMatcher m = i.next(); + + if (((m.getListener().equals(listener)) || (listener == null)) && + ((m.getExpression().equals(pattern)) || (pattern == null))) { + i.remove(); + } + } + } + // Else: Both null => Just ignore that dummy call! + } + + /** + * Returns the list of rules that match the element path and + * attributes. + * + * @param path the current element path. + * @param attrs the attributes of the element. + * + * @return the list of matching rules or null if + * no match was found. + */ + private Collection getMatchingRules(String path, Attributes attrs) { + Collection matchingRules = null; + + for (XPathMatcher rule : this.listeners) { + if (rule.match(path, attrs)) { + if (matchingRules == null) { + matchingRules = new ArrayList(); + } + matchingRules.add(rule); + } + } + return (matchingRules); + } + + //------------------------------------------------------------------------- + // SAXBuilder / SAXHandler configuration helper methods + //------------------------------------------------------------------------- + + /** + * Sets a custom JDOMFactory for the builder. Use this to build + * the tree with your own subclasses of the JDOM classes. + * + * @param factory JDOMFactory to use. + */ + public void setFactory(JDOMFactory factory) { + this.parserBuilder.setJDOMFactory(factory); + } + + /** + * Activates or deactivates validation for the builder. + * + * @param validate whether XML validation should occur. + */ + public void setValidation(boolean validate) { + if (validate) + this.parserBuilder.setXMLReaderFactory(XMLReaders.DTDVALIDATING); + else + this.parserBuilder.setXMLReaderFactory(XMLReaders.NONVALIDATING); + } + + /** + * Specifies whether or not the parser should elminate whitespace + * in element content (sometimes known as "ignorable whitespace") + * when building the document. Only whitespace which is contained + * within element content that has an element only content model + * will be eliminated (see XML Rec 3.2.1). For this setting to + * take effect requires that validation be turned on. + *

+ * The default value is false.

+ * + * @param ignoringWhite whether to ignore ignorable whitespace. + */ + public void setIgnoringElementContentWhitespace(boolean ignoringWhite) { + this.parserBuilder.setIgnoringElementContentWhitespace(ignoringWhite); + } + + /** + * Sets whether or not to expand entities for the builder. + *

+ * A value true means to expand entities as normal + * content; false means to leave entities unexpanded + * as EntityRef objects.

+ *

+ * The default value is true.

+ * + * @param expand whether entity expansion should occur. + */ + public void setExpandEntities(boolean expand) { + this.parserBuilder.setExpandEntities(expand); + } + + //------------------------------------------------------------------------- + // XMLFilterImpl overwritten methods + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // XMLReader interface support + //------------------------------------------------------------------------- + + /** + * Sets the state of a feature. + * + * @param name the feature name, which is a fully-qualified + * URI. + * @param state the requested state of the feature. + * + * @throws SAXNotRecognizedException when the XMLReader does not + * recognize the feature name. + * @throws SAXNotSupportedException when the XMLReader + * recognizes the feature name but cannot set the + * requested value. + */ + @Override + public void setFeature(String name, boolean state) + throws SAXNotRecognizedException, SAXNotSupportedException { + if (this.getParent() != null) { + this.getParent().setFeature(name, state); + } + this.parserBuilder.setFeature(name, state); + } + + /** + * Set the value of a property. + * + * @param name the property name, which is a fully-qualified + * URI. + * @param value the requested value for the property. + * + * @throws SAXNotRecognizedException when the XMLReader does not + * recognize the property name. + * @throws SAXNotSupportedException when the XMLReader + * recognizes the property name but cannot set the + * requested value. + */ + @Override + public void setProperty(String name, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException { + if (this.getParent() != null) { + this.getParent().setProperty(name, value); + } + this.parserBuilder.setProperty(name, value); + } + + /** + * Parses an XML document. + *

+ * The application can use this method to instruct ElementScanner + * to begin parsing an XML document from any valid input source + * (a character stream, a byte stream, or a URI).

+ *

+ * Applications may not invoke this method while a parse is in + * progress. Once a parse is complete, an application may reuse + * the same ElementScanner object, possibly with a different input + * source.

+ *

+ * This method is synchronous: it will not return until parsing + * has ended. If a client application wants to terminate parsing + * early, it should throw an exception.

+ * + * @param source the input source for the XML document. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + * @throws IOException an IO exception from the parser, + * possibly from a byte stream or character + * stream supplied by the application. + */ + @Override + public void parse(InputSource source) throws IOException, SAXException { + final SAXHandler shandler = new FragmentHandler(parserBuilder.getJDOMFactory()); + SAXHandlerFactory shfactory = new SAXHandlerFactory() { + @Override + public SAXHandler createSAXHandler(JDOMFactory fac) { + // ignore the fac. + return shandler; + } + }; + final XMLReaderJDOMFactory currentfac = parserBuilder.getXMLReaderFactory(); + final SAXHandlerFactory currentshfac = parserBuilder.getSAXHandlerFactory(); + try { + final XMLReader xreader = getParent() != null + ? getParent() + : parserBuilder.getXMLReaderFactory().createXMLReader(); + final boolean validating = currentfac.isValidating(); + parserBuilder.setSAXHandlerFactory(shfactory); + parserBuilder.setXMLReaderFactory(new XMLReaderJDOMFactory() { + @Override + public boolean isValidating() { + return validating; + } + + @Override + public XMLReader createXMLReader() throws JDOMException { + return xreader; + } + }); + // configures the sax handler and parser to mate. + parserBuilder.buildEngine(); + // Allocate the element builder (SAXHandler subclass). + this.saxHandler = shandler; + + // Allocate (if not provided) and configure the parent parser. + if (this.getParent() != null) { + setParent(xreader); + } + + } catch (JDOMException e) { + throw new SAXException("Problem in JDOM.", e); + } finally { + parserBuilder.setXMLReaderFactory(currentfac); + parserBuilder.setSAXHandlerFactory(currentshfac); + } + // And delegate to superclass now that everything has been set-up. + // Note: super.parse() forces the registration of this filter as + // ContentHandler, ErrorHandler, DTDHandler and EntityResolver. + super.parse(source); + } + + //------------------------------------------------------------------------- + // ContentHandler interface support + //------------------------------------------------------------------------- + + /** + * [ContentHandler interface support] Receives notification + * of the beginning of a document. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void startDocument() throws SAXException { + // Reset state. + this.currentPath.setLength(0); + this.activeRules.clear(); + + // Propagate event. + this.saxHandler.startDocument(); + super.startDocument(); + } + + /** + * [ContentHandler interface support] Receives notification + * of the end of a document. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void endDocument() throws SAXException { + // Propagate event. + this.saxHandler.endDocument(); + super.endDocument(); + } + + /** + * [ContentHandler interface support] Begins the scope of + * a prefix-URI Namespace mapping. + * + * @param prefix the Namespace prefix being declared. + * @param uri the Namespace URI the prefix is mapped to. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void startPrefixMapping(String prefix, String uri) + throws SAXException { + // Propagate event. + this.saxHandler.startPrefixMapping(prefix, uri); + super.startPrefixMapping(prefix, uri); + } + + /** + * [ContentHandler interface support] Ends the scope of a + * prefix-URI Namespace mapping. + * + * @param prefix the prefix that was being mapped. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void endPrefixMapping(String prefix) throws SAXException { + // Propagate event. + this.saxHandler.endPrefixMapping(prefix); + super.endPrefixMapping(prefix); + } + + /** + * [ContentHandler interface support] Receives notification + * of the beginning of an element. + * + * @param nsUri the Namespace URI, or the empty string if + * the element has no Namespace URI or if + * Namespace processing is not being performed. + * @param localName the local name (without prefix), or the + * empty string if Namespace processing is + * not being performed. + * @param qName the qualified name (with prefix), or the + * empty string if qualified names are not + * available. + * @param attrs the attributes attached to the element. If + * there are no attributes, it shall be an + * empty Attributes object. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void startElement(String nsUri, String localName, + String qName, Attributes attrs) + throws SAXException { + // Append new element to the current path. + this.currentPath.append('/').append(localName); + + // Retrieve the matching rules for this element. + String eltPath = this.currentPath.substring(0); + Collection matchingRules = this.getMatchingRules(eltPath, attrs); + if (matchingRules != null) { + // Matching rules found. + // => Make them active to trigger element building. + this.activeRules.put(eltPath, matchingRules); + } + + // Propagate event. + if (this.activeRules.size() != 0) { + this.saxHandler.startElement(nsUri, localName, qName, attrs); + } + super.startElement(nsUri, localName, qName, attrs); + } + + /** + * [ContentHandler interface support] Receives notification + * of the end of an element. + * + * @param nsUri the Namespace URI, or the empty string if + * the element has no Namespace URI or if + * Namespace processing is not being performed. + * @param localName the local name (without prefix), or the + * empty string if Namespace processing is + * not being performed. + * @param qName the qualified name (with prefix), or the + * empty string if qualified names are not + * available. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void endElement(String nsUri, String localName, String qName) + throws SAXException { + // Grab the being-built element. + Element elt = this.saxHandler.getCurrentElement(); + + // Complete element building before making use of it. + // (This sets the current element to the parent of elt.) + if (this.activeRules.size() != 0) { + this.saxHandler.endElement(nsUri, localName, qName); + } + + // Get the matching rules for this element (if any). + String eltPath = this.currentPath.substring(0); + Collection matchingRules = this.activeRules.remove(eltPath); + if (matchingRules != null) { + // Matching rules found. + // => Detach the current element if no rules remain active. + if (this.activeRules.size() == 0) { + elt.detach(); + } + + // And notify all matching listeners. + try { + for (XPathMatcher matcher : matchingRules) { + if (matcher.match(eltPath, elt)) { + matcher.getListener().elementMatched(eltPath, elt); + } + } + } + catch (JDOMException ex1) { + // Oops! Listener-originated exception. + // => Fire a SAXException to abort parsing. + throw (new SAXException(ex1.getMessage(), ex1)); + } + } + // Remove notified element from the current path. + this.currentPath.setLength( + this.currentPath.length() - (localName.length() + 1)); + // Propagate event. + super.endElement(nsUri, localName, qName); + } + + /** + * [ContentHandler interface support] Receives notification + * of character data. + * + * @param ch the characters from the XML document. + * @param start the start position in the array. + * @param length the number of characters to read from the array. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void characters(char[] ch, int start, int length) + throws SAXException { + // Propagate event. + if (this.activeRules.size() != 0) { + this.saxHandler.characters(ch, start, length); + } + super.characters(ch, start, length); + } + + /** + * [ContentHandler interface support] Receives notification + * of ignorable whitespace in element content. + * + * @param ch the characters from the XML document. + * @param start the start position in the array. + * @param length the number of characters to read from the array. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void ignorableWhitespace(char[] ch, int start, int length) + throws SAXException { + // Propagate event. + if (this.activeRules.size() != 0) { + this.saxHandler.ignorableWhitespace(ch, start, length); + } + super.ignorableWhitespace(ch, start, length); + } + + /** + * [ContentHandler interface support] Receives notification + * of processing instruction. + * + * @param target the processing instruction target. + * @param data the processing instruction data, or + * null if none was supplied. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void processingInstruction(String target, String data) + throws SAXException { + // Propagate event. + if (this.activeRules.size() != 0) { + this.saxHandler.processingInstruction(target, data); + } + super.processingInstruction(target, data); + } + + /** + * [ContentHandler interface support] Receives notification + * of a skipped entity. + * + * @param name the name of the skipped entity. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void skippedEntity(String name) throws SAXException { + // Propagate event. + if (this.activeRules.size() != 0) { + this.saxHandler.skippedEntity(name); + } + super.skippedEntity(name); + } + + + //========================================================================= + // Deviant implementations of JDOM builder objects + //========================================================================= + + //------------------------------------------------------------------------- + // ParserBuilder nested class + //------------------------------------------------------------------------- + + //------------------------------------------------------------------------- + // FragmentHandler nested class + //------------------------------------------------------------------------- + + /** + * FragmentHandler extends SAXHandler to support matching nodes + * without a common ancestor. This class inserts a dummy root + * element in the being-built document. This prevents the document + * to have, from SAXHandler's point of view, multiple root + * elements (which would cause the parse to fail). + */ + private static class FragmentHandler extends SAXHandler { + /** + * Public constructor. + */ + public FragmentHandler(JDOMFactory factory) { + super(factory); + + // Add a dummy root element to the being-built document. + this.pushElement(new Element("root", null, null)); + } + } + +} + diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/JakartaRegExpXPathMatcher.java b/contrib/src/java/org/jdom/contrib/input/scanner/JakartaRegExpXPathMatcher.java new file mode 100644 index 0000000..d337702 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/JakartaRegExpXPathMatcher.java @@ -0,0 +1,169 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.input.scanner; + + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; + +import org.xml.sax.Attributes; + + +/* package */ class JakartaRegExpXPathMatcher extends XPathMatcher { + + /** + * The compiled regular expression this matcher matches. + */ + private final Pattern re; + + private final XPathExpression test; + + /** + * Creates a new XPath matcher using Jakarta RegExp regular + * expression matcher implementation. + * + * @param expression the XPath-like expression to match. + * @param listener the element listener to notify when an + * element matches the expression. + * + * @throws JDOMException if one of the arguments is invalid. + */ + public JakartaRegExpXPathMatcher( + String expression, ElementListener listener) + throws JDOMException { + super(expression, listener); + + try { + String pathPattern = getPathPatternAsRE(expression); + + this.re = Pattern.compile(pathPattern); + + String testPattern = getTestPattern(expression); + if (testPattern != null) { + testPattern = "." + testPattern; + + this.test = XPathFactory.instance().compile(testPattern); + } + else { + this.test = null; + } + + if (isDebug()) { + System.out.println("Listener " + listener + ":"); + System.out.println(" " + expression + + " -> RE = " + pathPattern); + System.out.println(" " + expression + + " -> XPath = " + testPattern); + } + } + catch (PatternSyntaxException ex1) { + throw (new JDOMException( + "Illegal XPath expression: " + expression, ex1)); + } + } + + /** + * Tries to match an element path and attributes with the XPath + * expression this matcher matches. + *

+ * This method is invoked when processing the + * startElement SAX event.

+ *

+ * This implementation only matches the element path, ignoring + * the attributes.

+ * + * @param path the path to the element. + * @param attrs the SAX attributes of the element. + * + * @return true is the element matches the XPath + * expression, false otherwise. + */ + @Override +public boolean match(String path, Attributes attrs) { + return (this.re.matcher(path).matches()); + } + + /** + * Tries to match an element with the XPath expression this + * matcher matches. + *

+ * This method is invoked when processing the + * endElement SAX event. It allows matching on + * data not part of the startElement event such + * as the presence of child elements.

+ *

+ * This implementation always return true as it + * does not support matching on anything but the path.

+ * + * @param path the path to the element. + * @param elt the JDOM element. + * + * @return true is the element matches the XPath + * expression, false otherwise. + */ + @Override +public boolean match(String path, Element elt) { + if (this.test != null) { + return !this.test.evaluate(elt).isEmpty(); + } + return (true); + } +} + diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/README.txt b/contrib/src/java/org/jdom/contrib/input/scanner/README.txt new file mode 100644 index 0000000..96a2017 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/README.txt @@ -0,0 +1,40 @@ + +ElementScanner is a SAX filter that uses XPath-like expressions to select +element nodes to build and notifies listeners when these elements becomes +available during the SAX parse. + +ElementScanner does not aim at providing a faster parsing of XML documents. +Its primary focus is to allow the application to control the parse and to +consume the XML data while they are being parsed. ElementScanner can be +viewed as a high-level SAX parser that fires events conveying JDOM elements +rather that XML tags and character data. +ElementScanner only notifies of the parsing of element nodes and does not +support reporting the parsing of DOCTYPE data, processing instructions or +comments except for those present within the selected elements. +Applications needing such data shall register a specific SAX ContentHandler +on ElementScanner to receive them in the form of raw SAX events. + +To use this package, in addition to JDOM, the following products must be +present in the application class path: + - Jakarta Regexp 1.1 or higher + (see "http://jakarta.apache.org/regexp/index.html") + - Jaxen 1.0 beta7 or higher + (see "http://www.jaxen.org/") + +For detailed information, please refer to the package Javadoc documentation +or the file "package.html" in this directory. + + +The "doc-files directory contains simple test cases that demonstrate how to +use ElementScanner within an application: + + - ElementScannerTest.java is a simple program that uses ElementScanner + to parse the XML file passed as argument and registers a set of + ElementListeners that display the parsed elements. + Usage: java ElementScannerTest [XML file] + + - test.xml is an example of XML file that can be used with the above + sample. + + +-- Laurent Bihanic diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/XPathMatcher.java b/contrib/src/java/org/jdom/contrib/input/scanner/XPathMatcher.java new file mode 100644 index 0000000..11ef435 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/XPathMatcher.java @@ -0,0 +1,398 @@ +/*-- + + Copyright (C) 2001-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.input.scanner; + + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Constructor; +import java.lang.reflect.Modifier; + +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.internal.SystemProperty; + +import org.xml.sax.Attributes; + + +/** + * The base class for all XPath matchers used by + * {@link ElementScanner}. + *

+ * This class also plays the role of factory for concrete + * implementations: The system property + * "org.jdom.XPathMatcher.class" shall contain the + * fully-qualified name of a concrete subclass of XPatchMatcher with + * a public {@link #XPathMatcher two argument constructor}. If this + * property is not defined, the default concrete implementation + * (supporting only node matching patterns) will be used.

+ *

+ * As the default implementation relies on Perl5-like regular + * expression to match nodes, any regular expression can be used as + * "XPath expression" with the restriction that any '*' + * character be escaped (i.e. preceded with a '\' character).

+ * + * @author Laurent Bihanic + */ +public abstract class XPathMatcher { + + /** + * The name of the system property from which to retrieve the + * name of the implementation class to use. + *

+ * The property name is: + * "org.jdom.XPathMatcher.class".

+ */ + private final static String IMPLEMENTATION_CLASS_PROPERTY = + "org.jdom.XPathMatcher.class"; + + /** + * The default implementation class to use if none was configured. + */ + private final static String DEFAULT_IMPLEMENTATION_CLASS = + "org.jdom.contrib.input.scanner.JakartaRegExpXPathMatcher"; + + /** + * The constructor to instanciate a new XPathMatcher concrete + * implementation. + * + * @see #newXPathMatcher + */ + private static Constructor constructor = null; + + /** + * Whether debug traces are active. + */ + private static boolean debug = false; + + /** + * The XPath expression this matcher matches! + */ + private final String expression; + + /** + * The element listener. + */ + private final ElementListener listener; + + /** + * Default constructor, protected on purpose. + * + * @param expression the XPath-like expression to match. + * @param listener the element listener to notify when an + * element matches the expression. + * + * @throws JDOMException if one of the arguments is invalid. + */ + protected XPathMatcher(String expression, ElementListener listener) + throws JDOMException { + if ((expression == null) || (expression.length() == 0)) { + throw (new JDOMException( + "Invalid XPath expression: \"" + expression + "\"")); + } + if (listener == null) { + throw (new JDOMException("Invalid ElementListener: ")); + } + this.expression = expression; + this.listener = listener; + } + + /** + * Returns the XPath expression this matcher matches. + * + * @return the XPath expression this matcher matches. + */ + public String getExpression() { + return (this.expression); + } + + /** + * Returns the element listener associated to this matcher object. + * + * @return the element listener. + */ + public ElementListener getListener() { + return (this.listener); + } + + /** + * Tries to match an element path and attributes with the XPath + * expression this matcher matches. + *

+ * This method is invoked when processing the + * startElement SAX event.

+ *

+ * Note: The default implementation ignores the + * attributes.

+ * + * @param path the path to the element. + * @param attrs the SAX attributes of the element. + * + * @return true is the element matches the XPath + * expression, false otherwise. + */ + abstract public boolean match(String path, Attributes attrs); + + /** + * Tries to match an element with the XPath expression this + * matcher matches. + *

+ * This method is invoked when processing the + * endElement SAX event. It allows matching on + * data not part of the startElement event such + * as the presence of child elements.

+ *

+ * Note: The default implementation always + * returns true as it only supports filtering on + * the node path.

+ * + * @param path the path to the element. + * @param elt the JDOM element. + * + * @return true is the element matches the XPath + * expression, false otherwise. + */ + abstract public boolean match(String path, Element elt); + + /** + * Extracts the node matching pattern part from an XPath + * expression and converts it into a Perl5-like regular + * expression. + * + * @param expr an XPath expression. + * + * @return the RE to match the element path. + * + * @throws JDOMException if expression is invalid. + */ + protected static String getPathPatternAsRE(String expr) + throws JDOMException { + if ((expr == null) || (expr.length() == 0)) { + expr = "/*"; + } + + // It the expression ends with a square backet, a test part is + // present. => Strip it! + // Note: Any other sub-expression between square backet is view as + // a RE alphabet definition and proccessed. OK, that's not + // XPath compliant but that's very convenient! + String path = (expr.endsWith("]"))? + expr.substring(0, expr.lastIndexOf('[')): expr; + + int length = path.length(); + StringBuilder re = new StringBuilder(2 * length); + + char previous = (char)0; + for (int i=0; i Remove previous '\' and insert '*'. + re.setLength(re.length() - 1); + } + else { + // Regular XPath wildcard. + // => "*" -> ".[^/]*", i.e. all chars but the separator '/' + re.append(".[^/]"); + } + re.append('*'); + } + else { + if ((current == '/') && (previous == '/')) { + // "//" -> "/.*/" or "/" !!! + // => Remove previous '/' + re.setLength(re.length() - 1); + + // And insert RE. + re.append("(/.*/|/)"); + } + else { + re.append(current); + } + } + previous = current; + } + re.append('$'); + + return (re.toString()); + } + + /** + * Extracts the test part from an XPath expression. The test + * part if the part of the XPath expression between square + * backets. + * + * @param expr an XPath expression. + * + * @return the test part of the expression of null + * if no test was specified. + * + * @throws JDOMException if expression is invalid. + */ + protected static String getTestPattern(String expr) throws JDOMException { + if (expr.endsWith("]")) { + return (expr.substring(expr.lastIndexOf('['))); + } + return (null); + } + + + //------------------------------------------------------------------------- + // XPathMatcher Factory methods + //------------------------------------------------------------------------- + + /** + * Activates or deactivates debug traces on the process standard + * output. Debug traces allow to trace the mapping between the + * XPath-like expressions provided when registering a listener + * and the regular expressions and/or XPath expressions actually + * used to filter elements. + * + * @param value whether to activate debug traces. + */ + public static void setDebug(boolean value) { + debug = value; + } + + /** + * Returns whether debug traces are active. + * + * @return true if debug traces are active; + * false otherwise. + */ + public static boolean isDebug() { + return (debug); + } + + /** + * Sets the concrete XPathMatcher subclass to be used when + * allocating XPathMatcher instances. + * + * @param aClass the concrete subclass of XPathMatcher. + * + * @throws IllegalArgumentException if aClass is + * null. + * @throws JDOMException if aClass is + * not a concrete subclass + * of XPathMatcher. + */ + public static void setXPathMatcherClass(Class aClass) + throws IllegalArgumentException, JDOMException { + if (aClass != null) { + try { + if ((XPathMatcher.class.isAssignableFrom(aClass)) && + (Modifier.isAbstract(aClass.getModifiers()) == false)) { + // Concrete subclass pf XPathMatcher. + // => Get the constructor to use. + constructor = aClass.getConstructor(String.class, ElementListener.class); + } + else { + throw (new JDOMException( + aClass.getName() + " is not a concrete XPathMatcher")); + } + } + catch (Exception ex1) { + // Any reflection error (probably due to a configuration mistake). + throw (new JDOMException(ex1.toString(), ex1)); + } + } + else { + throw (new IllegalArgumentException("aClass")); + } + } + + /** + * Creates a new XPath matcher matching the specified XPath + * expression. + * + * @param expression the XPath-like expression to match. + * @param listener the element listener to notify when an + * element matches the expression. + * @return a matcher. + * + * @throws JDOMException if expression is invalid. + */ + public static final XPathMatcher newXPathMatcher( + String expression, ElementListener listener) + throws JDOMException { + try { + if (constructor == null) { + // First call. + // => Load configuration to determine implementation. + String className = SystemProperty.get( + IMPLEMENTATION_CLASS_PROPERTY, + DEFAULT_IMPLEMENTATION_CLASS); + + setXPathMatcherClass(Class.forName(className)); + } + return (XPathMatcher)constructor.newInstance(expression, listener); + } + catch (InvocationTargetException ex1) { + // Constructor threw an error on invocation. + Throwable te = ex1.getTargetException(); + + throw ((te instanceof JDOMException)? (JDOMException)te: + new JDOMException(te.toString(), te)); + } + catch (Exception ex3) { + // Any reflection error (probably due to a configuration mistake). + throw (new JDOMException(ex3.toString(), ex3)); + } + } +} + diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/doc-files/ElementScannerTest.java b/contrib/src/java/org/jdom/contrib/input/scanner/doc-files/ElementScannerTest.java new file mode 100644 index 0000000..0ea9f33 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/doc-files/ElementScannerTest.java @@ -0,0 +1,64 @@ +import java.io.IOException; + +import org.xml.sax.InputSource; + +import org.jdom.Element; +import org.jdom.output.XMLOutputter2; + +import org.jdom.contrib.input.scanner.ElementScanner; +import org.jdom.contrib.input.scanner.ElementListener; + + +@SuppressWarnings("javadoc") +public class ElementScannerTest +{ + public static XMLOutputter2 out = new XMLOutputter2(); + + public static void main(String[] args) throws Exception + { + org.jdom.contrib.input.scanner.XPathMatcher.setDebug(true); + + ElementScanner scanner = new ElementScanner(); + + scanner.addElementListener(new Spy("Listener #1 - \"x//y\""), "x//y"); + scanner.addElementListener(new Spy("Listener #2 - \"y/*/y\""), "y/*/y"); + scanner.addElementListener(new Spy("Listener #3 - \"/*\""), "/*"); + scanner.addElementListener(new Spy("Listener #4 - \"z\""), "z"); + + scanner.addElementListener(new Spy("Listener #5 - \"*[contains(@name,'.1')]\""), + "*[contains(@name,'.1')]"); + scanner.addElementListener(new Spy("Listener #6 - \"y[.//y]\""), + "y[.//y]"); + + String input = "test.xml"; + if (args.length > 0) + { + input = args[0]; + } + + scanner.parse(new InputSource(input)); + } + + private static class Spy implements ElementListener + { + private String name; + + public Spy(String name) + { + this.name = name; + } + + @Override + public void elementMatched(String path, Element e) + { + try + { + System.out.print(this.name + "\n>>> " + path + "\n"); + out.output(e, System.out); + System.out.println("\n<<<\n"); + } + catch (IOException ex1) { ex1.printStackTrace(); } + } + } +} + diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/doc-files/test.xml b/contrib/src/java/org/jdom/contrib/input/scanner/doc-files/test.xml new file mode 100644 index 0000000..7e83177 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/doc-files/test.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/src/java/org/jdom/contrib/input/scanner/package.html b/contrib/src/java/org/jdom/contrib/input/scanner/package.html new file mode 100644 index 0000000..9190d8a --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/input/scanner/package.html @@ -0,0 +1,30 @@ + + + ElementScanner is a SAX filter that uses XPath-like expressions to + select element nodes to build and notifies listeners when these + elements becomes available during the SAX parse. +

+ ElementScanner does not aim at providing a faster parsing of XML + documents. Its primary focus is to allow the application to + control the parse and to consume the XML data while they are + being parsed. ElementScanner can be viewed as a high-level SAX + parser that fires events conveying JDOM + {@link org.jdom.Element elements} rather that XML tags and + character data.

+

+ ElementScanner only notifies of the parsing of element nodes and + does not support reporting the parsing of DOCTYPE data, processing + instructions or comments except for those present within the + selected elements. Application needing such data shall register + a specific {@link org.xml.sax.ContentHandler} of this filter to + receive them in the form of raw SAX events.

+

+ Please refer to ElementScanner + for details on how to use ElementScanner within an application.

+

+ A sample application is also provided + here, with an example + XML file.

+ + + diff --git a/contrib/src/java/org/jdom/contrib/output/JTreeOutputter.java b/contrib/src/java/org/jdom/contrib/output/JTreeOutputter.java new file mode 100644 index 0000000..d95626c --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/output/JTreeOutputter.java @@ -0,0 +1,136 @@ +/*-- + + Copyright (C) 2000-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.output; + +/** + * A JTree outputter. + *

+ * This outputter builds a JTree representation of the JDOM document for + * easy visual navigation. This is a full rewrite of the JTreeOutputter + * originally written by James Davies. + *

+ * + * @author Matthew MacKenzie [matt@xmlglobal.com] + */ +import java.util.Iterator; +import javax.swing.tree.DefaultMutableTreeNode; +import org.jdom.Document; +import org.jdom.Attribute; +import org.jdom.Element; + +@SuppressWarnings("javadoc") +public class JTreeOutputter { + + public JTreeOutputter() { + } + + /** + * @param toBeCompatible unused. + */ + public JTreeOutputter(boolean toBeCompatible) { + // just here to mimic the legacy JTreeOutputter + } + + /** + * Output a Document. + * @param doc The document to transform to TreeNode. + * @param root The root tree node. + */ + public void output(Document doc, DefaultMutableTreeNode root) { + processElement(doc.getRootElement(),root); + } + + /** + * Output an Element. + * @param el The element to transform to TreeNode. + * @param root The root tree node. + */ + public void output(Element el, DefaultMutableTreeNode root) { + processElement(el, root); + } + + protected void processElement(Element el, DefaultMutableTreeNode dmtn) { + DefaultMutableTreeNode dmtnLocal = + new DefaultMutableTreeNode(el.getName()); + String elText = el.getTextNormalize(); + if (elText != null && !elText.equals("")) { + dmtnLocal.add(new DefaultMutableTreeNode(elText)); + } + processAttributes(el, dmtnLocal); + + Iterator iter = el.getChildren().iterator(); + + while (iter.hasNext()) { + Element nextEl = iter.next(); + processElement(nextEl, dmtnLocal); + } + dmtn.add(dmtnLocal); + } + + protected void processAttributes(Element el, DefaultMutableTreeNode dmtn) { + if (!el.hasAttributes()) { + return; + } + Iterator atts = el.getAttributes().iterator(); + while (atts.hasNext()) { + Attribute att = atts.next(); + DefaultMutableTreeNode node = + new DefaultMutableTreeNode("@" + att.getName()); + node.add(new DefaultMutableTreeNode(att.getValue())); + dmtn.add(node); + } + } +} + diff --git a/contrib/src/java/org/jdom/contrib/output/README b/contrib/src/java/org/jdom/contrib/output/README new file mode 100644 index 0000000..6037a6f --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/output/README @@ -0,0 +1 @@ +Location for development of experimental JDOM outputters. diff --git a/contrib/src/java/org/jdom/contrib/perf/DevNull.java b/contrib/src/java/org/jdom/contrib/perf/DevNull.java new file mode 100644 index 0000000..664d08d --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/perf/DevNull.java @@ -0,0 +1,133 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + +package org.jdom.contrib.perf; + +import java.io.IOException; +import java.io.Writer; + +final class DevNull extends Writer { + + int counter = 0; + + public void reset() { + counter = 0; + } + + public int getCounter() { + return counter; + } + + @Override + public void write(final char[] cbuf, final int off, final int len) throws IOException { + // do nothing + counter++; + } + + @Override + public void flush() throws IOException { + // do nothing + counter++; + } + + @Override + public void close() throws IOException { + // do nothing + counter++; + } + + @Override + public void write(final int c) throws IOException { + // do nothing + counter++; + } + + @Override + public void write(final char[] cbuf) throws IOException { + // do nothing + counter++; + } + + @Override + public void write(final String str) throws IOException { + // do nothing + counter++; + } + + @Override + public void write(final String str, final int off, final int len) throws IOException { + // do nothing + counter++; + } + + @Override + public Writer append(final CharSequence csq) throws IOException { + counter++; + return this; + } + + @Override + public Writer append(final CharSequence csq, final int start, final int end) throws IOException { + counter++; + return this; + } + + @Override + public Writer append(final char c) throws IOException { + counter++; + return this; + } + +} \ No newline at end of file diff --git a/contrib/src/java/org/jdom/contrib/perf/PerfDoc.java b/contrib/src/java/org/jdom/contrib/perf/PerfDoc.java new file mode 100644 index 0000000..5ebe1db --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/perf/PerfDoc.java @@ -0,0 +1,541 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + +package org.jdom.contrib.perf; + +import java.io.CharArrayReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; + +import org.jdom.filter.ElementFilter; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.DefaultHandler2; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.UncheckedJDOMFactory; +import org.jdom.input.DOMBuilder; +import org.jdom.input.SAXBuilder; +import org.jdom.input.StAXEventBuilder; +import org.jdom.input.StAXStreamBuilder; +import org.jdom.input.sax.SAXHandler; +import org.jdom.internal.ArrayCopy; +import org.jdom.output.Format; +import org.jdom.output.SAXOutputter; +import org.jdom.output.XMLOutputter2; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings("javadoc") +public class PerfDoc { + + private class SAXLoadRunnable implements TimeRunnable { + private final int type; + + SAXLoadRunnable(int type) { + this.type = type; + } + + @Override + public void run() throws Exception { + Document doc = subload(type); + if (document == null) { + document = doc; + } + } + } + + private static final DevNull devnull = new DevNull(); + + private final File infile; + + private long saxTime = -1L; + private long domTime = -1L; + private long staxTime = -1L; + private long staxETime = -1L; + private long saxDTime = -1L; + private long domDTime = -1L; + private long staxDTime = -1L; + private long staxDETime = -1L; + private long loadMem = -1L; + + private long dumpTime = -1L; + private long xpathTime = -1L; + private long dupeTime = -1L; + private long scanTime = -1L; + private long checkedTime = -1L; + private long uncheckedTime = -1L; + + + private Document document = null; + private final char[] chars; + + public PerfDoc(File file) throws IOException { + infile = file; + char[] fchars = new char[(int)infile.length()]; + int len = 0; + int cnt = 0; + FileReader fr = null; + try { + fr = new FileReader(infile); + while ((cnt = fr.read(fchars, len, fchars.length - len)) >= 0) { + if (cnt == 0 && len == fchars.length) { + fchars = ArrayCopy.copyOf(fchars, fchars.length + 10240); + } + len += cnt; + } + fchars = ArrayCopy.copyOf(fchars, len); + } finally { + if (fr != null) { + fr.close(); + } + fr = null; + } + this.chars = fchars; + } + + public File getFile() { + return infile; + } + + public long saxLoad() throws Exception { + final long startmem = PerfTest.usedMem(); + saxTime = PerfTest.timeRun(new SAXLoadRunnable(0) ); + loadMem = PerfTest.usedMem() - startmem; + saxDTime = PerfTest.timeRun(new SAXLoadRunnable(8) ); + return saxTime; + } + + public long domLoad() throws Exception { + domTime = PerfTest.timeRun( new SAXLoadRunnable(1) ); + domDTime = PerfTest.timeRun( new SAXLoadRunnable(9) ); + return domTime; + } + + public long staxLoad() throws Exception { + staxTime = PerfTest.timeRun( new SAXLoadRunnable(2) ); + staxDTime = PerfTest.timeRun( new SAXLoadRunnable(10) ); + return staxTime; + } + + public long staxELoad() throws Exception { + staxETime = PerfTest.timeRun( new SAXLoadRunnable(3) ); + staxDETime = PerfTest.timeRun( new SAXLoadRunnable(11) ); + return staxETime; + } + + public Document subload(int type) throws Exception { + + CharArrayReader car = new CharArrayReader(chars); + switch (type) { + case 0: + SAXBuilder sax = new SAXBuilder(); + sax.setJDOMFactory(new UncheckedJDOMFactory()); + return sax.build(car); + case 1: + DOMBuilder dom = new DOMBuilder(); + dom.setFactory(new UncheckedJDOMFactory()); + InputSource source = new InputSource(car); + return dom.build(getDocument(source, false)); + case 2: + StAXStreamBuilder stax = new StAXStreamBuilder(); + stax.setFactory(new UncheckedJDOMFactory()); + XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader(car); + return stax.build(reader); + case 3: + StAXEventBuilder staxe = new StAXEventBuilder(); + staxe.setFactory(new UncheckedJDOMFactory()); + XMLEventReader events = XMLInputFactory.newInstance().createXMLEventReader(car); + return staxe.build(events); + case 8: + SAXBuilder dsax = new SAXBuilder(); + DefaultHandler2 def = new DefaultHandler2(); + XMLReader sread = dsax.getXMLReaderFactory().createXMLReader(); + sread.setContentHandler(def); + sread.setDTDHandler(def); + sread.setEntityResolver(def); + sread.setErrorHandler(def); + sread.setProperty("http://xml.org/sax/properties/lexical-handler", + def); + sread.parse(new InputSource(car)); + return null; + case 9: + InputSource dsource = new InputSource(car); + getDocument(dsource, false); + return null; + case 10: + XMLStreamReader dreader = XMLInputFactory.newInstance().createXMLStreamReader(car); + int sum = 0; + while (dreader.hasNext()) { + sum += dreader.next(); + } + System.out.println("Sum " + sum); + return null; + case 11: + XMLEventReader dereader = XMLInputFactory.newInstance().createXMLEventReader(car); + int esum = 0; + while (dereader.hasNext()) { + esum += dereader.nextEvent().getEventType(); + } + System.out.println("Sum " + esum); + return null; + case 12: + SAXBuilder slimsax = new SAXBuilder(); + //slimsax.setJDOMFactory(new SlimJDOMFactory()); + //slimsax.setFeature("http://xml.org/sax/features/string-interning", true); + return slimsax.build(car); + } + return null; + } + + public int recurse(Element emt) { + int cnt = 1; + for (Object kid : emt.getChildren()) { + cnt += recurse((Element)kid); + } + return cnt; + } + + public long scan() throws Exception { + scanTime = PerfTest.timeRun(new TimeRunnable() { + @Override + public void run() { + int elements = 0; + Iterator it = document.getDescendants(); + while (it.hasNext()) { + if (it.next() instanceof Element) { + elements++; + } + } + + int felements = 0; + Iterator et = document.getDescendants(new ElementFilter()); + while (et.hasNext()) { + et.next(); + felements++; + } + if (felements != elements) { + System.out.printf("Different counts Descendants=%d Elements=%d\n", elements, felements); + } + + int rcnt = recurse(document.getRootElement()); + if (rcnt != elements) { + System.out.printf("Different counts Descendants=%d Recurse=%d\n", elements, rcnt); + } + } + }); + return scanTime; + } + + public long dump () throws Exception { + dumpTime = PerfTest.timeRun(new TimeRunnable() { + + @Override + public void run() throws Exception { + long start = System.nanoTime(); + dump(Format.getCompactFormat()); + long comp = System.nanoTime(); + dump(Format.getPrettyFormat()); + long pretty = System.nanoTime(); + dump(Format.getRawFormat()); + long raw = System.nanoTime(); + raw -= pretty; + pretty -= comp; + comp -= start; + System.out.printf("Raw=%d Pretty=%8d Compact=%8d\n", raw, pretty, comp); + } + }); + return dumpTime; + } + + private void dump(Format format) throws IOException { + XMLOutputter2 xout = new XMLOutputter2(format); + devnull.reset(); + System.setProperty("NamespaceStack", ""); + xout.output(document, devnull); + System.clearProperty("NamespaceStack"); + if (devnull.getCounter() <= 0) { + throw new IllegalStateException("Needed a counter"); + } + } + + public long xpath() throws Exception { + + final XPathFactory fac = XPathFactory.instance(); + + xpathTime = PerfTest.timeRun(new TimeRunnable() { + @Override + public void run() throws Exception { + XPathExpression patha = fac.compile("//@null"); + patha.evaluate(document); + // select everything + XPathExpression pathb = fac.compile("//node()"); + pathb.evaluate(document); + } + }); + return xpathTime; + } + + private CollectionduplicateContent(List content) { + ArrayListret = new ArrayList(content.size()); + for (Content c : content) { + if (c instanceof Element) { + Element emt = (Element)c; + Element ne = new Element(emt.getName(), emt.getNamespacePrefix(), emt.getNamespaceURI()); + if (emt.hasAttributes()) { + for (Object oatt : emt.getAttributes()) { + Attribute att = (Attribute)oatt; + Attribute a = new Attribute(att.getName(), att.getValue(), + att.getAttributeType(), + Namespace.getNamespace(att.getNamespacePrefix(), att.getNamespaceURI())); + emt.setAttribute(a); + } + } + if (emt.hasAdditionalNamespaces()) { + for (Object ns : emt.getAdditionalNamespaces()) { + ne.addNamespaceDeclaration((Namespace)ns); + } + } + ne.addContent(duplicateContent(emt.getContent())); + ret.add(ne); + } else if (c instanceof CDATA) { + ret.add(new CDATA(((CDATA)c).getText())); + } else if (c instanceof Comment) { + ret.add(new Comment(((Comment)c).getText())); + } else if (c instanceof EntityRef) { + EntityRef er = (EntityRef)c; + ret.add(new EntityRef(er.getName(), er.getPublicID(), er.getSystemID())); + } else if (c instanceof Text) { + ret.add(new Text(((Text)c).getText())); + } else if (c instanceof ProcessingInstruction) { + ProcessingInstruction pi = (ProcessingInstruction)c; + ret.add(new ProcessingInstruction(pi.getTarget(), pi.getData())); + } else if (c instanceof DocType) { + DocType dt = (DocType)c; + DocType ndt = new DocType(dt.getElementName(), dt.getPublicID(), dt.getSystemID()); + if (dt.getInternalSubset() != null) { + ndt.setInternalSubset(dt.getInternalSubset()); + } + ret.add(ndt); + } else { + throw new IllegalStateException("Unknown content " + c); + } + } + return ret; + } + + public long duplicate() throws Exception { + final XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + final String orig = xout.outputString(document); + dupeTime = PerfTest.timeRun(new TimeRunnable() { + @Override + public void run() throws Exception { + // select nothing. + Document doc = new Document(); + doc.addContent(duplicateContent(document.getContent())); + String dupe = xout.outputString(doc); + if (!orig.equals(dupe)) { + throw new IllegalStateException("Bad clone!"); + } + } + }); + return dupeTime; + } + + public long checkedParse() throws Exception { + final XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + final String orig = xout.outputString(document); + checkedTime = PerfTest.timeRun(new TimeRunnable() { + @Override + public void run() throws Exception { + // select nothing. + SAXHandler handler = new SAXHandler(new DefaultJDOMFactory()); + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + saxout.output(document); + Document doc = handler.getDocument(); + String dupe = xout.outputString(doc); + if (!orig.equals(dupe)) { + throw new IllegalStateException("Bad clone!"); + } + } + }); + return checkedTime; + } + + public long unCheckedParse() throws Exception { + final XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + final String orig = xout.outputString(document); + uncheckedTime = PerfTest.timeRun(new TimeRunnable() { + @Override + public void run() throws Exception { + // select nothing. + SAXHandler handler = new SAXHandler(new UncheckedJDOMFactory()); + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + saxout.output(document); + Document doc = handler.getDocument(); + String dupe = xout.outputString(doc); + if (!orig.equals(dupe)) { + throw new IllegalStateException("Bad clone!"); + } + } + }); + return uncheckedTime; + } + + public long getSAXLoadTime() { + return saxTime; + } + + + public long getLoadMem() { + return loadMem; + } + + + public long getDumpTime() { + return dumpTime; + } + + public long getDupeTime() { + return dupeTime; + } + + public long getXpathTime() { + return xpathTime; + } + + public long getCheckedTime() { + return checkedTime; + } + + public long getUncheckedTime() { + return uncheckedTime; + } + + + public long getScanTime() { + return scanTime; + } + + + + public long getSaxDTime() { + return saxDTime; + } + + public long getDomDTime() { + return domDTime; + } + + public long getStaxDTime() { + return staxDTime; + } + + + public long getStaxETime() { + return staxETime; + } + + public long getStaxDETime() { + return staxDETime; + } + + public Document getDocument() { + return document; + } + + @Override + public String toString() { + return String.format("PerfDoc %s mem=%d sax=%d dom=%d stax=%d scan=%d xpath=%d dump=%d", + infile.getPath(), loadMem, saxTime, domTime, staxTime, scanTime, xpathTime, dumpTime); + } + + private static final org.w3c.dom.Document getDocument(InputSource data, boolean xsdvalidate) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setValidating(xsdvalidate); + dbf.setExpandEntityReferences(false); + + if (xsdvalidate) { + dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); + } + DocumentBuilder db = dbf.newDocumentBuilder(); + return db.parse(data); + } +} diff --git a/contrib/src/java/org/jdom/contrib/perf/PerfTest.java b/contrib/src/java/org/jdom/contrib/perf/PerfTest.java new file mode 100644 index 0000000..790746c --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/perf/PerfTest.java @@ -0,0 +1,383 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + +package org.jdom.contrib.perf; + +import java.io.File; +import java.io.FileFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + + +@SuppressWarnings("javadoc") +public class PerfTest { + + public static final long timeRun(TimeRunnable torun) throws Exception { + long min = Long.MAX_VALUE; + long max = Long.MIN_VALUE; + long sum = 0L; + final int cnt = 12; + for (int i = 0 ; i < cnt; i++) { + System.gc(); + long time = System.nanoTime(); + torun.run(); + time = System.nanoTime() - time; + if (time > max) { + max = time; + } + if (time < min) { + min = time; + } + sum += time; + } + return (sum - (min + max)) / (cnt - 2); + } + + + public static final long usedMem() { + long mem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + boolean stable = false; + Thread gct = new Thread("GCPrompt") { + @Override + public void run() { + System.gc(); + } + }; + gct.setDaemon(true); + try { + gct.start(); + Thread.sleep(100); + gct.join(); + } catch (Exception e) { + // ignore; + } + do { + int cnt = 3; + while (--cnt >= 0) { + System.gc(); + } + long tmpmem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + long diff = mem - tmpmem; + mem = tmpmem; + if (diff >= 0 && diff < 128) { + stable = true; + } + } while (!stable); + return mem; + } + + private static final String[] suffix = new String[]{"Bytes", "KiB", "MiB", "GiB", "TiB" }; + + private static final String formatMem(long mem) { + double frac = mem; + int cnt = 0; + while (frac > 1024.0) { + frac /= 1024.0; + cnt++; + } + return String.format("%.2f%s", frac, suffix[cnt]); + } + + /** + * @param args + */ + public static void main(String[] args) { + ArrayList infiles = new ArrayList(); + + final FileFilter filefilter = new FileFilter() { + @Override + public boolean accept(File path) { + return path.isFile() && path.getName().toLowerCase().endsWith(".xml"); + } + }; + + for (String arg : args) { + File f = new File(arg); + if (f.exists()) { + if (f.isFile() && f.getName().toLowerCase().endsWith(".xml")) { + infiles.add(f); + } else if (f.isDirectory()) { + File[] files = f.listFiles(filefilter); + if (files.length > 0) { + for (File sf : files) { + infiles.add(sf); + } + } else { + System.err.println("Ignoring File " + f + ": Directory has no XML files."); + } + } else { + System.err.println("Ignoring File " + f + ": not .xml and not Directory."); + } + } else { + System.err.println("Ignoring File " + f + ": does not exist."); + } + } + + if (infiles.isEmpty()) { + File tryhamlet = new File("contrib/src/resources/hamlet.xml"); + if (tryhamlet.exists()) { + System.out.println("Using default file " + tryhamlet); + infiles.add(tryhamlet); + } + } + + System.out.printf("Processing %d files.\n", infiles.size()); + + StringBuilder html = new StringBuilder(); + + perfloop(infiles, html); + perfloop(infiles, html); + System.out.println("Ignore the above warm-up results!\n\n"); + + html.setLength(0); + html.append("\n\t
\n\t

\n\tDescription - change me\n\t
\n\t\n\t\t"); + for (String h : new String[] {"Input", "JDOM", "SAX", "SAXJ", "DOM", "DOMJ", + "StAXS", "StAXSJ", "StAXE", "StAXEJ", "Scan", "Dump", + "Dupe", "XPath", "Checked", "UnChecked"}) { + html.append(""); + } + html.append("\n"); + + for (int i = 0; i < 5; i++) { + perfloop(infiles, html); + } + + html.append("\t
").append(h).append("
\n"); + + System.out.println(html.toString()); + } + + private static final void perfloop(List infiles, StringBuilder html) { + + double mstime = 1000000.0; + + PerfDoc[] docs = new PerfDoc[infiles.size()]; + int cnt = 0; + long bytecnt = 0L; + for (File f : infiles) { + try { + docs[cnt++] = new PerfDoc(f); + } catch (IOException e) { + throw new IllegalStateException("Unable to load " + f, e); + } + bytecnt += f.length(); + } + + long loadmem = 0L; + long saxtime = 0L; + long saxdtime = 0L; + + + long startmem = usedMem(); + for (PerfDoc pd : docs) { + try { + saxtime += pd.saxLoad(); + loadmem += pd.getLoadMem(); + saxdtime += pd.getSaxDTime(); +// System.out.println("Loaded " + pd.getFile()); + } catch (Exception e) { + System.err.println("Failed to load " + pd); + e.printStackTrace(); + } + } + + long actloadmem = usedMem() - startmem; + + double pctdiff = ((actloadmem - loadmem) * 100.0) / actloadmem; + if (pctdiff < 0.0) { + pctdiff = -1.0 * pctdiff; + } + if (pctdiff > 1.0) { + throw new IllegalStateException( + String.format("Memory calculations do not add up direct=%s sum=%s.", + formatMem(actloadmem), formatMem(loadmem))); + } + + usedMem(); + + long domtime = 0L; + long domdtime = 0L; + for (PerfDoc pd : docs) { + try { + domtime += pd.domLoad(); + domdtime += pd.getDomDTime(); + } catch (Exception e) { + System.err.println("Failed to DOM " + pd); + e.printStackTrace(); + } + } + + usedMem(); + + long staxtime = 0L; + long staxdtime = 0L; + for (PerfDoc pd : docs) { + try { + staxtime += pd.staxLoad(); + staxdtime += pd.getStaxDTime(); + } catch (Exception e) { + System.err.println("Failed to STAX " + pd); + e.printStackTrace(); + } + } + + usedMem(); + + long staxetime = 0L; + long staxdetime = 0L; + for (PerfDoc pd : docs) { + try { + staxetime += pd.staxELoad(); + staxdetime += pd.getStaxDETime(); + } catch (Exception e) { + System.err.println("Failed to STAX " + pd); + e.printStackTrace(); + } + } + + long scantime = 0L; + for (PerfDoc pd : docs) { + try { + scantime += pd.scan(); + } catch (Exception e) { + System.err.println("Failed to scan " + pd); + e.printStackTrace(); + } + } + + usedMem(); + + long dumptime = 0L; + for (PerfDoc pd : docs) { + try { + dumptime += pd.dump(); + } catch (Exception e) { + System.err.println("Failed to dump " + pd); + e.printStackTrace(); + } + } + + usedMem(); + + long dupetime = 0L; + for (PerfDoc pd : docs) { + try { + dupetime += pd.duplicate(); + } catch (Exception e) { + System.err.println("Failed to dupe " + pd); + e.printStackTrace(); + } + } + + usedMem(); + + long xpathtime = 0L; + for (PerfDoc pd : docs) { + try { + xpathtime += pd.xpath(); + } catch (Exception e) { + System.err.println("Failed to xpath " + pd); + e.printStackTrace(); + } + } + + usedMem(); + + long checkedtime = 0L; + for (PerfDoc pd : docs) { + try { + checkedtime += pd.checkedParse(); + } catch (Exception e) { + System.err.println("Failed to checked-parse " + pd); + e.printStackTrace(); + } + } + + long uncheckedtime = 0L; + for (PerfDoc pd : docs) { + try { + uncheckedtime += pd.unCheckedParse(); + } catch (Exception e) { + System.err.println("Failed to unchecked-parse " + pd); + e.printStackTrace(); + } + } + + System.out.printf ("PERF: loadbytes=%s loadmem=%s sax=%.2fb(%.2fb) dom=%.2fb(%.2fb) " + + "staxs=%.2fb(%.2fb) staxe=%.2fb(%.2fb) " + + "scan=%.2fb dump=%.2fb dupe=%.2fb xpath=%.2fb checked=%.2fb unchecked=%.2fb \n", + formatMem(bytecnt), formatMem(loadmem), saxtime / mstime, saxdtime / mstime, + domtime / mstime, domdtime / mstime, staxtime / mstime, staxdtime / mstime, + staxetime / mstime, staxdetime / mstime, + scantime / mstime, dumptime / mstime, dupetime / mstime, + xpathtime / mstime, checkedtime / mstime, uncheckedtime / mstime); + + html.append("\t\t").append(formatMem(bytecnt)).append("") + .append(formatMem(loadmem)).append(""); + + long[] times = new long[] {saxtime, saxtime - saxdtime, domtime, domtime - domdtime, + staxtime, staxtime - staxdtime, staxetime, staxetime - staxdetime, + scantime, dumptime, dupetime, + xpathtime, checkedtime, uncheckedtime}; + for (long t : times) { + html.append("").append(String.format("%.2fms", t / mstime)).append(""); + } + html.append("\n"); + + + } + +} diff --git a/contrib/src/java/org/jdom/contrib/perf/PerfVerifier.java b/contrib/src/java/org/jdom/contrib/perf/PerfVerifier.java new file mode 100644 index 0000000..1b62187 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/perf/PerfVerifier.java @@ -0,0 +1,224 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + +package org.jdom.contrib.perf; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.ArrayList; + +import org.jdom.Verifier; + +/** + * This class is designed to test the performance of the JDOM Verifier. + * It does that by repeatedly parsing a document through a verifierd JDOM sequence, and then + * comparing the time to a non-verified parsing. The time differences is accounted for by + * verifying overhead. + * + * @author Rolf Lear + * + */ +public class PerfVerifier { + + @SuppressWarnings("javadoc") + public static void main(final String[] args) throws InterruptedException { + Thread.currentThread().setPriority(Thread.MAX_PRIORITY); + if (args.length != 1) { + throw new IllegalArgumentException("We expect a single directory argument."); + } + final File dir = new File(args[0]); + if (!dir.isDirectory()) { + throw new IllegalArgumentException("We expect a single directory argument."); + } + + final int bestcnt = 50; + + long[] sattnanos = new long[bestcnt]; + long[] semtnanos = new long[bestcnt]; + long[] schrnanos = new long[bestcnt]; + long start = 0L; + + System.out.println("Loading data"); + + final String[] attnames = parseFile(new File(dir, "checkAttributeName.txt")); + final String[] emtnames = parseFile(new File(dir, "checkElementName.txt")); + final String[] chardata = parseFile(new File(dir, "checkCharacterData.txt")); + + + System.out.println("Stabilize"); + Thread.sleep(5000); + final long prebytes = getMemUsed(); + + System.out.println("Launch"); + + int i = 0; + int cnt = bestcnt * 4; + while (--cnt >= 0) { + long attnanos = 0L; + long emtnanos = 0L; + long chrnanos = 0L; + + start = System.nanoTime(); + for (i = attnames.length - 1; i >= 0; i--) { + Verifier.checkAttributeName(attnames[i]); + } + attnanos = System.nanoTime() - start; + + start = System.nanoTime(); + for (i = emtnames.length - 1; i >= 0; i--) { + Verifier.checkElementName(emtnames[i]); + } + emtnanos = System.nanoTime() - start; + + start = System.nanoTime(); + + for (i = chardata.length - 1; i >= 0; i--) { + Verifier.checkCharacterData(chardata[i]); + } + chrnanos = System.nanoTime() - start; + + System.out.printf(" Loop %2d took: att=%.3fms emt=%.3fms char=%.3fms\n", cnt, + attnanos / 1000000.0, emtnanos / 1000000.0, chrnanos / 1000000.0); + + insertTime(sattnanos, attnanos); + insertTime(semtnanos, emtnanos); + insertTime(schrnanos, chrnanos); + + } + + final long memused = getMemUsed() - prebytes; + + System.out.printf(" Validating took: att=%.3fms emt=%.3fms char=%.3fms mem=%.3fKB\n", + avg(sattnanos) / 1000000.0, avg(semtnanos) / 1000000.0, avg(schrnanos) / 1000000.0, + memused / 1024.0); + + Verifier.isAllXMLWhitespace(" "); + System.out.println("Checks " + (chardata.length + emtnames.length + attnames.length)); + } + + private static final void insertTime(final long[] array, final long time) { + int index = array.length -1; + while (index >= 0 && (array[index] == 0L || time < array[index])) { + index--; + } + index++; + if (index < array.length) { + System.arraycopy(array, index, array, index+1, array.length - index - 1); + array[index] = time; + } + } + + private static final double avg(final long[] values) { + long ret = 0L; + for (long v : values) { + ret += v; + } + return ret / (double)values.length; + } + + private static long getMemUsed() { + long minused = Long.MAX_VALUE; + int cnt = 0; + final Runtime rt = Runtime.getRuntime(); + try { + while (cnt < 3) { + System.gc(); + Thread.yield(); + Thread.sleep(100); + long used = rt.totalMemory() - rt.freeMemory(); + if (used < minused) { + cnt = 0; + minused = used; + } else { + cnt++; + } + } + } catch (InterruptedException ie) { + throw new IllegalStateException("Interrupted", ie); + } + return minused; + } + + private static final String[] parseFile(File file) { + try { + final StringBuilder sb = new StringBuilder(1024); + final ArrayList vals = new ArrayList(10240); + final FileInputStream fis = new FileInputStream(file); + final InputStreamReader isr = new InputStreamReader(fis, Charset.forName("UTF-8")); + System.out.println("Loading " + file.getPath()); + int c = 0; + while ((c = isr.read()) >= 0) { + if (c == 0) { + vals.add(sb.toString()); + sb.setLength(0); + } else { + sb.append((char)c); + } + } + fis.close(); + + final String[] ret = new String[vals.size()]; + vals.toArray(ret); + return ret; + } catch (IOException ioe) { + ioe.printStackTrace(); + return null; + } + } + +} diff --git a/contrib/src/java/org/jdom/contrib/perf/SavingVerifier.java b/contrib/src/java/org/jdom/contrib/perf/SavingVerifier.java new file mode 100644 index 0000000..18da240 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/perf/SavingVerifier.java @@ -0,0 +1,1477 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.perf; + +import java.io.BufferedWriter; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DocType; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; + +/** + * NOTE + * ==== + * This is a clone of the pre-bitmask Verifier.java file, and then modified to 'save' the + * checked data from the verifier. Used with PerfVerifier to test the Verfier performance. + * + * + * Old Comments: + * ============= + * A utility class to handle well-formedness checks on names, data, and other + * verification tasks for JDOM. The class is final and may not be subclassed. + * + * @author Brett McLaughlin + * @author Elliotte Rusty Harold + * @author Jason Hunter + * @author Bradley S. Huffman + * @author Rolf Lear + */ +final public class SavingVerifier { + + private static final AtomicBoolean open = new AtomicBoolean(true); + + private static final Writer getWriter(final String name) { + try { + final FileOutputStream fos = new FileOutputStream(name); + final OutputStreamWriter osw = new OutputStreamWriter(fos, Charset.forName("UTF-8")); + return new BufferedWriter(osw); + } catch (IOException ioe) { + ioe.printStackTrace(); + open.set(false); + return null; + } + } + + private static final void write(Writer writer, String value) { + if (open.get()) { + try { + writer.write(value); + writer.write((char)0); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static final String elementfilename = "checkElementName.txt"; + private static final String attributefilename = "checkAttributeName.txt"; + private static final String chardatafilename = "checkCharacterData.txt"; + + private static final Writer elementnamewriter = getWriter(elementfilename); + private static final Writer attributenamewriter = getWriter(attributefilename); + private static final Writer characterdatawriter = getWriter(chardatafilename); + + @SuppressWarnings("javadoc") + public static final void closeWriters() throws IOException { + if (!open.getAndSet(false)) { + return; + } + open.set(false); + elementnamewriter.flush(); + elementnamewriter.close(); + attributenamewriter.flush(); + attributenamewriter.close(); + characterdatawriter.flush(); + characterdatawriter.close(); + } + + /** + * Ensure instantation cannot occur. + */ + private SavingVerifier() { } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Element} name. + * + * @param name String name to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkElementName(final String name) { + + write(elementnamewriter, name); + + // Check basic XML name rules first + if (checkXMLName(name) != null) { + return checkXMLName(name); + } + + // No colons allowed, since elements handle this internally + if (name.indexOf(":") != -1) { + return "Element names cannot contain colons"; + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Attribute} name. + * + * @param name String name to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkAttributeName(final String name) { + + write(attributenamewriter, name); + + // Check basic XML name rules first + if (checkXMLName(name) != null) { + return checkXMLName(name); + } + + // No colons are allowed, since attributes handle this internally + if (name.indexOf(":") != -1) { + return "Attribute names cannot contain colons"; + } + + // Attribute names may not be xmlns since we do this internally too + if (name.equals("xmlns")) { + return "An Attribute name may not be \"xmlns\"; " + + "use the Namespace class to manage namespaces"; + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied string to see if it only contains + * characters allowed by the XML 1.0 specification. The C0 controls + * (e.g. null, vertical tab, form-feed, etc.) are specifically excluded + * except for carriage return, line-feed, and the horizontal tab. + * Surrogates are also excluded. + *

+ * This method is useful for checking element content and attribute + * values. Note that characters + * like " and < are allowed in attribute values and element content. + * They will simply be escaped as " or < + * when the value is serialized. + *

+ * + * @param text String value to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkCharacterData(final String text) { + write(characterdatawriter, text); + if (text == null) { + return "A null is not a legal XML value"; + } + + // lowx indicates we expect a low surrogate next. + boolean lowx = false; + final int len = text.length(); + for (int i = 0; i < len; i++) { + // we are expecting a normal char, but may be a surrogate. + if (isXMLCharacter(text.charAt(i))) { + if (lowx) { + // we got a normal character, but we wanted a low surrogate + return String.format("Illegal Surrogate Pair 0x%04x%04x", + (int)text.charAt(i - 1), (int)text.charAt(i)); + } + } else { + // the character is not a normal character. + // we need to sort out what it is. Neither high nor low + // surrogate pairs are valid characters, so they will get here. + + if (!lowx && isHighSurrogate(text.charAt(i))) { + // we have the valid high char of a pair. + // we will expect the low char on the next loop through, + // so mark the high char, and move on. + lowx = true; + } else if (lowx && isLowSurrogate(text.charAt(i))) { + // we now have the low char of a pair, decode and validate + final int chi = decodeSurrogatePair( + text.charAt(i - 1), text.charAt(i)); + if (!isXMLCharacter(chi)) { + // Likely this character can't be easily displayed + // because it's a control so we use it'd hexadecimal + // representation in the reason. + return String.format("0x%06x is not a legal XML character", + chi); + } + lowx = false; + } else { + // Likely this character can't be easily displayed + // because it's a control so we use it's hexadecimal + // representation in the reason. + return String.format("0x%04x is not a legal XML character", + (int)text.charAt(i)); + } + } + } + + if (lowx) { + return String.format("Truncated Surrogate Pair 0x%04x????", + (int)text.charAt(text.length() - 1)); + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied data to see if it is legal for use as + * JDOM {@link CDATA}. + * + * @param data String data to check. + * @return String reason data is illegal, or + * null is name is OK. + */ + public static String checkCDATASection(final String data) { + String reason = null; + if ((reason = checkCharacterData(data)) != null) { + return reason; + } + + if (data.indexOf("]]>") != -1) { + return "CDATA cannot internally contain a CDATA ending " + + "delimiter (]]>)"; + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Namespace} prefix. + * + * @param prefix String prefix to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkNamespacePrefix(final String prefix) { + // Manually do rules, since URIs can be null or empty + if ((prefix == null) || (prefix.equals(""))) { + return null; + } + + // Cannot start with a number + final char first = prefix.charAt(0); + if (isXMLDigit(first)) { + return "Namespace prefixes cannot begin with a number"; + } + // Cannot start with a $ + if (first == '$') { + return "Namespace prefixes cannot begin with a dollar sign ($)"; + } + // Cannot start with a - + if (first == '-') { + return "Namespace prefixes cannot begin with a hyphen (-)"; + } + // Cannot start with a . + if (first == '.') { + return "Namespace prefixes cannot begin with a period (.)"; + } + // Cannot start with "xml" in any character case + if (prefix.toLowerCase().startsWith("xml")) { + return "Namespace prefixes cannot begin with " + + "\"xml\" in any combination of case"; + } + + // Ensure legal content + for (int i=0, len = prefix.length(); i{@link Namespace} URI. + *

+ * This is a 'light' test of URI's designed to filter out only the worst + * illegal URIs. It tests only to ensure the first character is valid. A + * comprehensive URI validation process would be impractical. + * + * @param uri String URI to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkNamespaceURI(final String uri) { + // Manually do rules, since URIs can be null or empty + if ((uri == null) || (uri.equals(""))) { + return null; + } + + // Cannot start with a number + final char first = uri.charAt(0); + if (Character.isDigit(first)) { + return "Namespace URIs cannot begin with a number"; + } + // Cannot start with a $ + if (first == '$') { + return "Namespace URIs cannot begin with a dollar sign ($)"; + } + // Cannot start with a - + if (first == '-') { + return "Namespace URIs cannot begin with a hyphen (-)"; + } + + // Cannot start with space... + if (isXMLWhitespace(first)) { + return "Namespace URIs cannot begin with white-space"; + } + + // If we got here, everything is OK + return null; + } + + /** + * Check if two namespaces collide. + * + * @param namespace Namespace to check. + * @param other Namespace to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Namespace other) { + String p1,p2,u1,u2,reason; + + reason = null; + p1 = namespace.getPrefix(); + u1 = namespace.getURI(); + p2 = other.getPrefix(); + u2 = other.getURI(); + if (p1.equals(p2) && !u1.equals(u2)) { + reason = "The namespace prefix \"" + p1 + "\" collides"; + } + return reason; + } + + /** + * Check if {@link Attribute}'s namespace collides with a + * {@link Element}'s namespace. + * + * @param attribute Attribute to check. + * @param element Element to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Attribute attribute, + final Element element) { + return checkNamespaceCollision(attribute, element, -1); + } + + /** + * Check if {@link Attribute}'s namespace collides with a + * {@link Element}'s namespace. + * + * @param attribute Attribute to check. + * @param element Element to check against. + * @param ignoreatt Ignore a specific Attribute (if it exists) when + * calculating any collisions (used when replacing one attribute + * with another). + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Attribute attribute, + final Element element, final int ignoreatt) { + final Namespace namespace = attribute.getNamespace(); + final String prefix = namespace.getPrefix(); + if ("".equals(prefix)) { + return null; + } + + return checkNamespaceCollision(namespace, element, ignoreatt); + } + + /** + * Check if a {@link Namespace} collides with a + * {@link Element}'s namespace. + * + * @param namespace Namespace to check. + * @param element Element to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Element element) { + return checkNamespaceCollision(namespace, element, -1); + } + + /** + * Check if a {@link Namespace} collides with a + * {@link Element}'s namespace. + * + * @param namespace Namespace to check. + * @param element Element to check against. + * @param ignoreatt Ignore a specific Attribute (if it exists) when + * calculating any collisions (used when replacing one attribute + * with another). + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Element element, final int ignoreatt) { + String reason = checkNamespaceCollision(namespace, + element.getNamespace()); + if (reason != null) { + return reason + " with the element namespace prefix"; + } + + if (element.hasAdditionalNamespaces()) { + reason = checkNamespaceCollision(namespace, + element.getAdditionalNamespaces()); + if (reason != null) { + return reason; + } + } + + if (element.hasAttributes()) { + reason = checkNamespaceCollision(namespace, element.getAttributes(), ignoreatt); + if (reason != null) { + return reason; + } + } + + return null; + } + + /** + * Check if a {@link Namespace} collides with a + * {@link Attribute}'s namespace. + * + * @param namespace Namespace to check. + * @param attribute Attribute to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Attribute attribute) { + String reason = null; + if (!attribute.getNamespace().equals(Namespace.NO_NAMESPACE)) { + reason = checkNamespaceCollision(namespace, + attribute.getNamespace()); + if (reason != null) { + reason += " with an attribute namespace prefix on the element"; + } + } + return reason; + } + + /** + * Check if a {@link Namespace} collides with any namespace + * from a list of objects. + * + * @param namespace Namespace to check. + * @param list List to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final List list) { + return checkNamespaceCollision(namespace, list, -1); + } + + /** + * Check if a {@link Namespace} collides with any namespace + * from a list of objects. + * + * @param namespace Namespace to check. + * @param list List to check against. + * @param ignoreatt Ignore a specific Attribute (if it exists) when + * calculating any collisions (used when replacing one attribute + * with another). + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final List list, final int ignoreatt) { + if (list == null) { + return null; + } + + String reason = null; + final Iterator i = list.iterator(); + int cnt = -1; + while ((reason == null) && i.hasNext()) { + final Object obj = i.next(); + cnt++; + if (obj instanceof Attribute) { + if (cnt == ignoreatt) { + continue; + } + reason = checkNamespaceCollision(namespace, (Attribute) obj); + } + else if (obj instanceof Element) { + reason = checkNamespaceCollision(namespace, (Element) obj); + } + else if (obj instanceof Namespace) { + reason = checkNamespaceCollision(namespace, (Namespace) obj); + if (reason != null) { + reason += " with an additional namespace declared" + + " by the element"; + } + } + } + return reason; + } + + /** + * This will check the supplied data to see if it is legal for use as + * a JDOM {@link ProcessingInstruction} target. + * + * @param target String target to check. + * @return String reason target is illegal, or + * null if target is OK. + */ + public static String checkProcessingInstructionTarget(final String target) { + // Check basic XML name rules first + String reason; + if ((reason = checkXMLName(target)) != null) { + return reason; + } + + // No colons allowed, per Namespace Specification Section 6 + if (target.indexOf(":") != -1) { + return "Processing instruction targets cannot contain colons"; + } + + // Cannot begin with 'xml' in any case + if (target.equalsIgnoreCase("xml")) { + return "Processing instructions cannot have a target of " + + "\"xml\" in any combination of case. (Note that the " + + "\"\" declaration at the beginning of a " + + "document is not a processing instruction and should not " + + "be added as one; it is written automatically during " + + "output, e.g. by XMLOutputter.)"; + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied data to see if it is legal for use as + * {@link ProcessingInstruction} data. Besides checking that + * all the characters are allowed in XML, this also checks + * that the data does not contain the PI end-string "?>". + * + * @param data String data to check. + * @return String reason data is illegal, or + * null if data is OK. + */ + public static String checkProcessingInstructionData(final String data) { + // Check basic XML name rules first + final String reason = checkCharacterData(data); + + if (reason == null) { + if (data.indexOf("?>") >= 0) { + return "Processing instructions cannot contain " + + "the string \"?>\""; + } + } + + return reason; + } + + /** + * This will check the supplied data to see if it is legal for use as + * JDOM {@link Comment} data. + * + * @param data String data to check. + * @return String reason data is illegal, or + * null if data is OK. + */ + public static String checkCommentData(final String data) { + String reason = null; + if ((reason = checkCharacterData(data)) != null) { + return reason; + } + + if (data.indexOf("--") != -1) { + return "Comments cannot contain double hyphens (--)"; + } + if (data.endsWith("-")) { + return "Comment data cannot end with a hyphen."; + } + + // If we got here, everything is OK + return null; + } + /** + * This is a utility function to decode a non-BMP + * UTF-16 surrogate pair. + * @param high high 16 bits + * @param low low 16 bits + * @return decoded character + */ + public static int decodeSurrogatePair(final char high, final char low) { + return 0x10000 + (high - 0xD800) * 0x400 + (low - 0xDC00); + } + + /** + * This will check the supplied data to see if it is legal for use as + * PublicID (in a {@link DocType} or {@link EntityRef}). + * + * @param c the character to validate + * @return String reason c is illegal, or + * null if c is OK. + */ + public static boolean isXMLPublicIDCharacter(final char c) { + // [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | + // [-'()+,./:=?;*#@$_%] + + if (c >= 'a' && c <= 'z') return true; + if (c >= '?' && c <= 'Z') return true; + if (c >= '\'' && c <= ';') return true; + + if (c == ' ') return true; + if (c == '!') return true; + if (c == '=') return true; + if (c == '#') return true; + if (c == '$') return true; + if (c == '_') return true; + if (c == '%') return true; + if (c == '\n') return true; + if (c == '\r') return true; + if (c == '\t') return true; + + return false; + } + + /** + * This will ensure that the data for a public identifier + * is legal. + * + * @param publicID String public ID to check. + * @return String reason public ID is illegal, or + * null if public ID is OK. + */ + public static String checkPublicID(final String publicID) { + String reason = null; + + if (publicID == null) return null; + // This indicates there is no public ID + + for (int i = 0; i < publicID.length(); i++) { + final char c = publicID.charAt(i); + if (!isXMLPublicIDCharacter(c)) { + reason = c + " is not a legal character in public IDs"; + break; + } + } + + return reason; + } + + + /** + * This will ensure that the data for a system literal + * is legal. + * + * @param systemLiteral String system literal to check. + * @return String reason system literal is illegal, or + * null if system literal is OK. + */ + public static String checkSystemLiteral(final String systemLiteral) { + String reason = null; + + if (systemLiteral == null) return null; + // This indicates there is no system ID + + if (systemLiteral.indexOf('\'') != -1 + && systemLiteral.indexOf('"') != -1) { + reason = + "System literals cannot simultaneously contain both single and double quotes."; + } + else { + reason = checkCharacterData(systemLiteral); + } + + return reason; + } + + /** + * This is a utility function for sharing the base process of checking + * any XML name. + * + * @param name String to check for XML name compliance. + * @return String reason the name is illegal, or + * null if OK. + */ + public static String checkXMLName(final String name) { + // Cannot be empty or null + if ((name == null)) { + return "XML names cannot be null"; + } + + final int len = name.length(); + if (len == 0) { + return "XML names cannot be empty"; + } + + + // Cannot start with a number + if (!isXMLNameStartCharacter(name.charAt(0))) { + return "XML names cannot begin with the character \"" + + name.charAt(0) + "\""; + } + // Ensure legal content for non-first chars + for (int i = 1; i < len; i++) { + if (!isXMLNameCharacter(name.charAt(i))) { + return "XML names cannot contain the character \"" + name.charAt(i) + "\""; + } + } + + // We got here, so everything is OK + return null; + } + + /** + *

+ * Checks a string to see if it is a legal RFC 2396 URI. + * Both absolute and relative URIs are supported. + *

+ * + * @param uri String to check. + * @return String reason the URI is illegal, or + * null if OK. + */ + public static String checkURI(final String uri) { + // URIs can be null or empty + if ((uri == null) || (uri.equals(""))) { + return null; + } + + for (int i = 0; i < uri.length(); i++) { + final char test = uri.charAt(i); + if (!isURICharacter(test)) { + String msgNumber = "0x" + Integer.toHexString(test); + if (test <= 0x09) msgNumber = "0x0" + Integer.toHexString(test); + return "URIs cannot contain " + msgNumber; + } // end if + if (test == '%') { // must be followed by two hexadecimal digits + try { + final char firstDigit = uri.charAt(i+1); + final char secondDigit = uri.charAt(i+2); + if (!isHexDigit(firstDigit) || + !isHexDigit(secondDigit)) { + return "Percent signs in URIs must be followed by " + + "exactly two hexadecimal digits."; + } + + } + catch (final StringIndexOutOfBoundsException e) { + return "Percent signs in URIs must be followed by " + + "exactly two hexadecimal digits."; + } + } + } // end for + + // If we got here, everything is OK + return null; + } + + /** + *

+ * This is a utility function for determining whether a specified + * Unicode character is a hexadecimal digit as defined in RFC 2396; + * that is, one of the ASCII characters 0-9, a-f, or A-F. + *

+ * + * @param c to check for hex digit. + * @return true if it's allowed, false otherwise. + */ + public static boolean isHexDigit(final char c) { + + // I suspect most characters passed to this method will be + // correct hexadecimal digits, so I test for the true cases + // first. If this proves to be a performance bottleneck + // a switch statement or lookup table + // might optimize this. + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'F') return true; + if (c >= 'a' && c <= 'f') return true; + + return false; + } + + /** + * This is a function for determining whether the + * specified character is the high 16 bits in a + * UTF-16 surrogate pair. + * @param ch character to check + * @return true if the character is a high surrogate, false otherwise + */ + public static boolean isHighSurrogate(final char ch) { + // faster way to do it is with bit manipulation.... + // return (ch >= 0xD800 && ch <= 0xDBFF); + // A high surrogate has the bit pattern: + // 110110xx xxxxxxxx + // ch & 0xFC00 does a bit-mask of the most significant 6 bits (110110) + // return 0xD800 == (ch & 0xFC00); + // as it happens, it is faster to do a bit-shift, + return 0x36 == ch >>> 10; + } + + /** + * This is a function for determining whether the + * specified character is the low 16 bits in a + * UTF-16 surrogate pair. + * @param ch character to check + * @return true if the character is a low surrogate, false otherwise. + */ + public static boolean isLowSurrogate(final char ch) { + // faster way to do it is with bit manipulation.... + // return (ch >= 0xDC00 && ch <= 0xDFFF); + return 0x37 == ch >>> 10; + } + + /** + *

+ * This is a utility function for determining whether + * a specified Unicode character is legal in URI references + * as determined by RFC 2396. + *

+ * + * @param c char to check for URI reference compliance. + * @return true if it's allowed, false otherwise. + */ + public static boolean isURICharacter(final char c) { + if (c >= 'a' && c <= 'z') return true; + if (c >= 'A' && c <= 'Z') return true; + if (c >= '0' && c <= '9') return true; + if (c == '/') return true; + if (c == '-') return true; + if (c == '.') return true; + if (c == '?') return true; + if (c == ':') return true; + if (c == '@') return true; + if (c == '&') return true; + if (c == '=') return true; + if (c == '+') return true; + if (c == '$') return true; + if (c == ',') return true; + if (c == '%') return true; + + if (c == '_') return true; + if (c == '!') return true; + if (c == '~') return true; + if (c == '*') return true; + if (c == '\'') return true; + if (c == '(') return true; + if (c == ')') return true; + return false; + } + + /** + * This is a utility function for determining whether a specified + * character is a character according to production 2 of the + * XML 1.0 specification. + * + * @param c char to check for XML compliance + * @return boolean true if it's a character, + * false otherwise + */ + public static boolean isXMLCharacter(final int c) { + + if (c == '\n') return true; + if (c == '\r') return true; + if (c == '\t') return true; + + if (c < 0x20) return false; if (c <= 0xD7FF) return true; + if (c < 0xE000) return false; if (c <= 0xFFFD) return true; + if (c < 0x10000) return false; if (c <= 0x10FFFF) return true; + + return false; + } + + + /** + * This is a utility function for determining whether a specified + * character is a name character according to production 4 of the + * XML 1.0 specification. + * + * @param c char to check for XML name compliance. + * @return boolean true if it's a name character, + * false otherwise. + */ + public static boolean isXMLNameCharacter(final char c) { + + return (isXMLLetter(c) || isXMLDigit(c) || c == '.' || c == '-' + || c == '_' || c == ':' || isXMLCombiningChar(c) + || isXMLExtender(c)); + } + + /** + * This is a utility function for determining whether a specified + * character is a legal name start character according to production 5 + * of the XML 1.0 specification. This production does allow names + * to begin with colons which the Namespaces in XML Recommendation + * disallows. + * + * @param c char to check for XML name start compliance. + * @return boolean true if it's a name start character, + * false otherwise. + */ + public static boolean isXMLNameStartCharacter(final char c) { + + return (isXMLLetter(c) || c == '_' || c ==':'); + + } + + /** + * This is a utility function for determining whether a specified + * character is a letter or digit according to productions 84 and 88 + * of the XML 1.0 specification. + * + * @param c char to check. + * @return boolean true if it's letter or digit, + * false otherwise. + */ + public static boolean isXMLLetterOrDigit(final char c) { + + return (isXMLLetter(c) || isXMLDigit(c)); + + } + + /** + * This is a utility function for determining whether a specified character + * is a letter according to production 84 of the XML 1.0 specification. + * + * @param c char to check for XML name compliance. + * @return String true if it's a letter, false otherwise. + */ + public static boolean isXMLLetter(final char c) { + // Note that order is very important here. The search proceeds + // from lowest to highest values, so that no searching occurs + // above the character's value. BTW, the first line is equivalent to: + // if (c >= 0x0041 && c <= 0x005A) return true; + + if (c < 0x0041) return false; if (c <= 0x005a) return true; + if (c < 0x0061) return false; if (c <= 0x007A) return true; + if (c < 0x00C0) return false; if (c <= 0x00D6) return true; + if (c < 0x00D8) return false; if (c <= 0x00F6) return true; + if (c < 0x00F8) return false; if (c <= 0x00FF) return true; + if (c < 0x0100) return false; if (c <= 0x0131) return true; + if (c < 0x0134) return false; if (c <= 0x013E) return true; + if (c < 0x0141) return false; if (c <= 0x0148) return true; + if (c < 0x014A) return false; if (c <= 0x017E) return true; + if (c < 0x0180) return false; if (c <= 0x01C3) return true; + if (c < 0x01CD) return false; if (c <= 0x01F0) return true; + if (c < 0x01F4) return false; if (c <= 0x01F5) return true; + if (c < 0x01FA) return false; if (c <= 0x0217) return true; + if (c < 0x0250) return false; if (c <= 0x02A8) return true; + if (c < 0x02BB) return false; if (c <= 0x02C1) return true; + if (c == 0x0386) return true; + if (c < 0x0388) return false; if (c <= 0x038A) return true; + if (c == 0x038C) return true; + if (c < 0x038E) return false; if (c <= 0x03A1) return true; + if (c < 0x03A3) return false; if (c <= 0x03CE) return true; + if (c < 0x03D0) return false; if (c <= 0x03D6) return true; + if (c == 0x03DA) return true; + if (c == 0x03DC) return true; + if (c == 0x03DE) return true; + if (c == 0x03E0) return true; + if (c < 0x03E2) return false; if (c <= 0x03F3) return true; + if (c < 0x0401) return false; if (c <= 0x040C) return true; + if (c < 0x040E) return false; if (c <= 0x044F) return true; + if (c < 0x0451) return false; if (c <= 0x045C) return true; + if (c < 0x045E) return false; if (c <= 0x0481) return true; + if (c < 0x0490) return false; if (c <= 0x04C4) return true; + if (c < 0x04C7) return false; if (c <= 0x04C8) return true; + if (c < 0x04CB) return false; if (c <= 0x04CC) return true; + if (c < 0x04D0) return false; if (c <= 0x04EB) return true; + if (c < 0x04EE) return false; if (c <= 0x04F5) return true; + if (c < 0x04F8) return false; if (c <= 0x04F9) return true; + if (c < 0x0531) return false; if (c <= 0x0556) return true; + if (c == 0x0559) return true; + if (c < 0x0561) return false; if (c <= 0x0586) return true; + if (c < 0x05D0) return false; if (c <= 0x05EA) return true; + if (c < 0x05F0) return false; if (c <= 0x05F2) return true; + if (c < 0x0621) return false; if (c <= 0x063A) return true; + if (c < 0x0641) return false; if (c <= 0x064A) return true; + if (c < 0x0671) return false; if (c <= 0x06B7) return true; + if (c < 0x06BA) return false; if (c <= 0x06BE) return true; + if (c < 0x06C0) return false; if (c <= 0x06CE) return true; + if (c < 0x06D0) return false; if (c <= 0x06D3) return true; + if (c == 0x06D5) return true; + if (c < 0x06E5) return false; if (c <= 0x06E6) return true; + if (c < 0x0905) return false; if (c <= 0x0939) return true; + if (c == 0x093D) return true; + if (c < 0x0958) return false; if (c <= 0x0961) return true; + if (c < 0x0985) return false; if (c <= 0x098C) return true; + if (c < 0x098F) return false; if (c <= 0x0990) return true; + if (c < 0x0993) return false; if (c <= 0x09A8) return true; + if (c < 0x09AA) return false; if (c <= 0x09B0) return true; + if (c == 0x09B2) return true; + if (c < 0x09B6) return false; if (c <= 0x09B9) return true; + if (c < 0x09DC) return false; if (c <= 0x09DD) return true; + if (c < 0x09DF) return false; if (c <= 0x09E1) return true; + if (c < 0x09F0) return false; if (c <= 0x09F1) return true; + if (c < 0x0A05) return false; if (c <= 0x0A0A) return true; + if (c < 0x0A0F) return false; if (c <= 0x0A10) return true; + if (c < 0x0A13) return false; if (c <= 0x0A28) return true; + if (c < 0x0A2A) return false; if (c <= 0x0A30) return true; + if (c < 0x0A32) return false; if (c <= 0x0A33) return true; + if (c < 0x0A35) return false; if (c <= 0x0A36) return true; + if (c < 0x0A38) return false; if (c <= 0x0A39) return true; + if (c < 0x0A59) return false; if (c <= 0x0A5C) return true; + if (c == 0x0A5E) return true; + if (c < 0x0A72) return false; if (c <= 0x0A74) return true; + if (c < 0x0A85) return false; if (c <= 0x0A8B) return true; + if (c == 0x0A8D) return true; + if (c < 0x0A8F) return false; if (c <= 0x0A91) return true; + if (c < 0x0A93) return false; if (c <= 0x0AA8) return true; + if (c < 0x0AAA) return false; if (c <= 0x0AB0) return true; + if (c < 0x0AB2) return false; if (c <= 0x0AB3) return true; + if (c < 0x0AB5) return false; if (c <= 0x0AB9) return true; + if (c == 0x0ABD) return true; + if (c == 0x0AE0) return true; + if (c < 0x0B05) return false; if (c <= 0x0B0C) return true; + if (c < 0x0B0F) return false; if (c <= 0x0B10) return true; + if (c < 0x0B13) return false; if (c <= 0x0B28) return true; + if (c < 0x0B2A) return false; if (c <= 0x0B30) return true; + if (c < 0x0B32) return false; if (c <= 0x0B33) return true; + if (c < 0x0B36) return false; if (c <= 0x0B39) return true; + if (c == 0x0B3D) return true; + if (c < 0x0B5C) return false; if (c <= 0x0B5D) return true; + if (c < 0x0B5F) return false; if (c <= 0x0B61) return true; + if (c < 0x0B85) return false; if (c <= 0x0B8A) return true; + if (c < 0x0B8E) return false; if (c <= 0x0B90) return true; + if (c < 0x0B92) return false; if (c <= 0x0B95) return true; + if (c < 0x0B99) return false; if (c <= 0x0B9A) return true; + if (c == 0x0B9C) return true; + if (c < 0x0B9E) return false; if (c <= 0x0B9F) return true; + if (c < 0x0BA3) return false; if (c <= 0x0BA4) return true; + if (c < 0x0BA8) return false; if (c <= 0x0BAA) return true; + if (c < 0x0BAE) return false; if (c <= 0x0BB5) return true; + if (c < 0x0BB7) return false; if (c <= 0x0BB9) return true; + if (c < 0x0C05) return false; if (c <= 0x0C0C) return true; + if (c < 0x0C0E) return false; if (c <= 0x0C10) return true; + if (c < 0x0C12) return false; if (c <= 0x0C28) return true; + if (c < 0x0C2A) return false; if (c <= 0x0C33) return true; + if (c < 0x0C35) return false; if (c <= 0x0C39) return true; + if (c < 0x0C60) return false; if (c <= 0x0C61) return true; + if (c < 0x0C85) return false; if (c <= 0x0C8C) return true; + if (c < 0x0C8E) return false; if (c <= 0x0C90) return true; + if (c < 0x0C92) return false; if (c <= 0x0CA8) return true; + if (c < 0x0CAA) return false; if (c <= 0x0CB3) return true; + if (c < 0x0CB5) return false; if (c <= 0x0CB9) return true; + if (c == 0x0CDE) return true; + if (c < 0x0CE0) return false; if (c <= 0x0CE1) return true; + if (c < 0x0D05) return false; if (c <= 0x0D0C) return true; + if (c < 0x0D0E) return false; if (c <= 0x0D10) return true; + if (c < 0x0D12) return false; if (c <= 0x0D28) return true; + if (c < 0x0D2A) return false; if (c <= 0x0D39) return true; + if (c < 0x0D60) return false; if (c <= 0x0D61) return true; + if (c < 0x0E01) return false; if (c <= 0x0E2E) return true; + if (c == 0x0E30) return true; + if (c < 0x0E32) return false; if (c <= 0x0E33) return true; + if (c < 0x0E40) return false; if (c <= 0x0E45) return true; + if (c < 0x0E81) return false; if (c <= 0x0E82) return true; + if (c == 0x0E84) return true; + if (c < 0x0E87) return false; if (c <= 0x0E88) return true; + if (c == 0x0E8A) return true; + if (c == 0x0E8D) return true; + if (c < 0x0E94) return false; if (c <= 0x0E97) return true; + if (c < 0x0E99) return false; if (c <= 0x0E9F) return true; + if (c < 0x0EA1) return false; if (c <= 0x0EA3) return true; + if (c == 0x0EA5) return true; + if (c == 0x0EA7) return true; + if (c < 0x0EAA) return false; if (c <= 0x0EAB) return true; + if (c < 0x0EAD) return false; if (c <= 0x0EAE) return true; + if (c == 0x0EB0) return true; + if (c < 0x0EB2) return false; if (c <= 0x0EB3) return true; + if (c == 0x0EBD) return true; + if (c < 0x0EC0) return false; if (c <= 0x0EC4) return true; + if (c < 0x0F40) return false; if (c <= 0x0F47) return true; + if (c < 0x0F49) return false; if (c <= 0x0F69) return true; + if (c < 0x10A0) return false; if (c <= 0x10C5) return true; + if (c < 0x10D0) return false; if (c <= 0x10F6) return true; + if (c == 0x1100) return true; + if (c < 0x1102) return false; if (c <= 0x1103) return true; + if (c < 0x1105) return false; if (c <= 0x1107) return true; + if (c == 0x1109) return true; + if (c < 0x110B) return false; if (c <= 0x110C) return true; + if (c < 0x110E) return false; if (c <= 0x1112) return true; + if (c == 0x113C) return true; + if (c == 0x113E) return true; + if (c == 0x1140) return true; + if (c == 0x114C) return true; + if (c == 0x114E) return true; + if (c == 0x1150) return true; + if (c < 0x1154) return false; if (c <= 0x1155) return true; + if (c == 0x1159) return true; + if (c < 0x115F) return false; if (c <= 0x1161) return true; + if (c == 0x1163) return true; + if (c == 0x1165) return true; + if (c == 0x1167) return true; + if (c == 0x1169) return true; + if (c < 0x116D) return false; if (c <= 0x116E) return true; + if (c < 0x1172) return false; if (c <= 0x1173) return true; + if (c == 0x1175) return true; + if (c == 0x119E) return true; + if (c == 0x11A8) return true; + if (c == 0x11AB) return true; + if (c < 0x11AE) return false; if (c <= 0x11AF) return true; + if (c < 0x11B7) return false; if (c <= 0x11B8) return true; + if (c == 0x11BA) return true; + if (c < 0x11BC) return false; if (c <= 0x11C2) return true; + if (c == 0x11EB) return true; + if (c == 0x11F0) return true; + if (c == 0x11F9) return true; + if (c < 0x1E00) return false; if (c <= 0x1E9B) return true; + if (c < 0x1EA0) return false; if (c <= 0x1EF9) return true; + if (c < 0x1F00) return false; if (c <= 0x1F15) return true; + if (c < 0x1F18) return false; if (c <= 0x1F1D) return true; + if (c < 0x1F20) return false; if (c <= 0x1F45) return true; + if (c < 0x1F48) return false; if (c <= 0x1F4D) return true; + if (c < 0x1F50) return false; if (c <= 0x1F57) return true; + if (c == 0x1F59) return true; + if (c == 0x1F5B) return true; + if (c == 0x1F5D) return true; + if (c < 0x1F5F) return false; if (c <= 0x1F7D) return true; + if (c < 0x1F80) return false; if (c <= 0x1FB4) return true; + if (c < 0x1FB6) return false; if (c <= 0x1FBC) return true; + if (c == 0x1FBE) return true; + if (c < 0x1FC2) return false; if (c <= 0x1FC4) return true; + if (c < 0x1FC6) return false; if (c <= 0x1FCC) return true; + if (c < 0x1FD0) return false; if (c <= 0x1FD3) return true; + if (c < 0x1FD6) return false; if (c <= 0x1FDB) return true; + if (c < 0x1FE0) return false; if (c <= 0x1FEC) return true; + if (c < 0x1FF2) return false; if (c <= 0x1FF4) return true; + if (c < 0x1FF6) return false; if (c <= 0x1FFC) return true; + if (c == 0x2126) return true; + if (c < 0x212A) return false; if (c <= 0x212B) return true; + if (c == 0x212E) return true; + if (c < 0x2180) return false; if (c <= 0x2182) return true; + if (c == 0x3007) return true; // ideographic + if (c < 0x3021) return false; if (c <= 0x3029) return true; // ideo + if (c < 0x3041) return false; if (c <= 0x3094) return true; + if (c < 0x30A1) return false; if (c <= 0x30FA) return true; + if (c < 0x3105) return false; if (c <= 0x312C) return true; + if (c < 0x4E00) return false; if (c <= 0x9FA5) return true; // ideo + if (c < 0xAC00) return false; if (c <= 0xD7A3) return true; + + return false; + + } + + /** + * This is a utility function for determining whether a specified character + * is a combining character according to production 87 + * of the XML 1.0 specification. + * + * @param c char to check. + * @return boolean true if it's a combining character, + * false otherwise. + */ + public static boolean isXMLCombiningChar(final char c) { + // CombiningChar + if (c < 0x0300) return false; if (c <= 0x0345) return true; + if (c < 0x0360) return false; if (c <= 0x0361) return true; + if (c < 0x0483) return false; if (c <= 0x0486) return true; + if (c < 0x0591) return false; if (c <= 0x05A1) return true; + + if (c < 0x05A3) return false; if (c <= 0x05B9) return true; + if (c < 0x05BB) return false; if (c <= 0x05BD) return true; + if (c == 0x05BF) return true; + if (c < 0x05C1) return false; if (c <= 0x05C2) return true; + + if (c == 0x05C4) return true; + if (c < 0x064B) return false; if (c <= 0x0652) return true; + if (c == 0x0670) return true; + if (c < 0x06D6) return false; if (c <= 0x06DC) return true; + + if (c < 0x06DD) return false; if (c <= 0x06DF) return true; + if (c < 0x06E0) return false; if (c <= 0x06E4) return true; + if (c < 0x06E7) return false; if (c <= 0x06E8) return true; + + if (c < 0x06EA) return false; if (c <= 0x06ED) return true; + if (c < 0x0901) return false; if (c <= 0x0903) return true; + if (c == 0x093C) return true; + if (c < 0x093E) return false; if (c <= 0x094C) return true; + + if (c == 0x094D) return true; + if (c < 0x0951) return false; if (c <= 0x0954) return true; + if (c < 0x0962) return false; if (c <= 0x0963) return true; + if (c < 0x0981) return false; if (c <= 0x0983) return true; + + if (c == 0x09BC) return true; + if (c == 0x09BE) return true; + if (c == 0x09BF) return true; + if (c < 0x09C0) return false; if (c <= 0x09C4) return true; + if (c < 0x09C7) return false; if (c <= 0x09C8) return true; + + if (c < 0x09CB) return false; if (c <= 0x09CD) return true; + if (c == 0x09D7) return true; + if (c < 0x09E2) return false; if (c <= 0x09E3) return true; + if (c == 0x0A02) return true; + if (c == 0x0A3C) return true; + + if (c == 0x0A3E) return true; + if (c == 0x0A3F) return true; + if (c < 0x0A40) return false; if (c <= 0x0A42) return true; + if (c < 0x0A47) return false; if (c <= 0x0A48) return true; + + if (c < 0x0A4B) return false; if (c <= 0x0A4D) return true; + if (c < 0x0A70) return false; if (c <= 0x0A71) return true; + if (c < 0x0A81) return false; if (c <= 0x0A83) return true; + if (c == 0x0ABC) return true; + + if (c < 0x0ABE) return false; if (c <= 0x0AC5) return true; + if (c < 0x0AC7) return false; if (c <= 0x0AC9) return true; + if (c < 0x0ACB) return false; if (c <= 0x0ACD) return true; + + if (c < 0x0B01) return false; if (c <= 0x0B03) return true; + if (c == 0x0B3C) return true; + if (c < 0x0B3E) return false; if (c <= 0x0B43) return true; + if (c < 0x0B47) return false; if (c <= 0x0B48) return true; + + if (c < 0x0B4B) return false; if (c <= 0x0B4D) return true; + if (c < 0x0B56) return false; if (c <= 0x0B57) return true; + if (c < 0x0B82) return false; if (c <= 0x0B83) return true; + + if (c < 0x0BBE) return false; if (c <= 0x0BC2) return true; + if (c < 0x0BC6) return false; if (c <= 0x0BC8) return true; + if (c < 0x0BCA) return false; if (c <= 0x0BCD) return true; + if (c == 0x0BD7) return true; + + if (c < 0x0C01) return false; if (c <= 0x0C03) return true; + if (c < 0x0C3E) return false; if (c <= 0x0C44) return true; + if (c < 0x0C46) return false; if (c <= 0x0C48) return true; + + if (c < 0x0C4A) return false; if (c <= 0x0C4D) return true; + if (c < 0x0C55) return false; if (c <= 0x0C56) return true; + if (c < 0x0C82) return false; if (c <= 0x0C83) return true; + + if (c < 0x0CBE) return false; if (c <= 0x0CC4) return true; + if (c < 0x0CC6) return false; if (c <= 0x0CC8) return true; + if (c < 0x0CCA) return false; if (c <= 0x0CCD) return true; + + if (c < 0x0CD5) return false; if (c <= 0x0CD6) return true; + if (c < 0x0D02) return false; if (c <= 0x0D03) return true; + if (c < 0x0D3E) return false; if (c <= 0x0D43) return true; + + if (c < 0x0D46) return false; if (c <= 0x0D48) return true; + if (c < 0x0D4A) return false; if (c <= 0x0D4D) return true; + if (c == 0x0D57) return true; + if (c == 0x0E31) return true; + + if (c < 0x0E34) return false; if (c <= 0x0E3A) return true; + if (c < 0x0E47) return false; if (c <= 0x0E4E) return true; + if (c == 0x0EB1) return true; + if (c < 0x0EB4) return false; if (c <= 0x0EB9) return true; + + if (c < 0x0EBB) return false; if (c <= 0x0EBC) return true; + if (c < 0x0EC8) return false; if (c <= 0x0ECD) return true; + if (c < 0x0F18) return false; if (c <= 0x0F19) return true; + if (c == 0x0F35) return true; + + if (c == 0x0F37) return true; + if (c == 0x0F39) return true; + if (c == 0x0F3E) return true; + if (c == 0x0F3F) return true; + if (c < 0x0F71) return false; if (c <= 0x0F84) return true; + + if (c < 0x0F86) return false; if (c <= 0x0F8B) return true; + if (c < 0x0F90) return false; if (c <= 0x0F95) return true; + if (c == 0x0F97) return true; + if (c < 0x0F99) return false; if (c <= 0x0FAD) return true; + + if (c < 0x0FB1) return false; if (c <= 0x0FB7) return true; + if (c == 0x0FB9) return true; + if (c < 0x20D0) return false; if (c <= 0x20DC) return true; + if (c == 0x20E1) return true; + + if (c < 0x302A) return false; if (c <= 0x302F) return true; + if (c == 0x3099) return true; + if (c == 0x309A) return true; + + return false; + + } + + /** + * This is a utility function for determining whether a specified + * character is an extender according to production 88 of the XML 1.0 + * specification. + * + * @param c char to check. + * @return String true if it's an extender, false otherwise. + */ + public static boolean isXMLExtender(final char c) { + + if (c < 0x00B6) return false; // quick short circuit + + // Extenders + if (c == 0x00B7) return true; + if (c == 0x02D0) return true; + if (c == 0x02D1) return true; + if (c == 0x0387) return true; + if (c == 0x0640) return true; + if (c == 0x0E46) return true; + if (c == 0x0EC6) return true; + if (c == 0x3005) return true; + + if (c < 0x3031) return false; if (c <= 0x3035) return true; + if (c < 0x309D) return false; if (c <= 0x309E) return true; + if (c < 0x30FC) return false; if (c <= 0x30FE) return true; + + return false; + + } + + /** + * This is a utility function for determining whether a specified + * Unicode character + * is a digit according to production 88 of the XML 1.0 specification. + * + * @param c char to check for XML digit compliance + * @return boolean true if it's a digit, false otherwise + */ + public static boolean isXMLDigit(final char c) { + + if (c < 0x0030) return false; if (c <= 0x0039) return true; + if (c < 0x0660) return false; if (c <= 0x0669) return true; + if (c < 0x06F0) return false; if (c <= 0x06F9) return true; + if (c < 0x0966) return false; if (c <= 0x096F) return true; + + if (c < 0x09E6) return false; if (c <= 0x09EF) return true; + if (c < 0x0A66) return false; if (c <= 0x0A6F) return true; + if (c < 0x0AE6) return false; if (c <= 0x0AEF) return true; + + if (c < 0x0B66) return false; if (c <= 0x0B6F) return true; + if (c < 0x0BE7) return false; if (c <= 0x0BEF) return true; + if (c < 0x0C66) return false; if (c <= 0x0C6F) return true; + + if (c < 0x0CE6) return false; if (c <= 0x0CEF) return true; + if (c < 0x0D66) return false; if (c <= 0x0D6F) return true; + if (c < 0x0E50) return false; if (c <= 0x0E59) return true; + + if (c < 0x0ED0) return false; if (c <= 0x0ED9) return true; + if (c < 0x0F20) return false; if (c <= 0x0F29) return true; + + return false; + } + + /** + * This is a utility function for determining whether a specified + * Unicode character is a whitespace character according to production 3 + * of the XML 1.0 specification. + * + * @param c char to check for XML whitespace compliance + * @return boolean true if it's a whitespace, false otherwise + */ + public static boolean isXMLWhitespace(final char c) { + // the following if is faster than switch statements. + // seems the implicit conversion to int is slower than + // the fall-through or's + if (c==' ' || c=='\n' || c=='\t' || c=='\r' ){ + return true; + } + return false; + } + + /** + * This is a utility function for determining whether a specified + * String is a whitespace character according to production 3 + * of the XML 1.0 specification. + *

+ * This method delegates the individual calls for each character to + * {@link #isXMLWhitespace(char)}. + * + * @param value + * The value to inspect + * @return true if all characters in the input value are all whitespace + * (or the string is the empty-string). + * @since JDOM2 + */ + public static final boolean isAllXMLWhitespace(final String value) { + // Doing the count-down instead of a count-up saves a single int + // variable declaration. + int i = value.length(); + while (--i >= 0) { + if (!isXMLWhitespace(value.charAt(i))) { + return false; + } + } + return true; + } + + +} diff --git a/contrib/src/java/org/jdom/contrib/perf/TimeRunnable.java b/contrib/src/java/org/jdom/contrib/perf/TimeRunnable.java new file mode 100644 index 0000000..a0ab0c5 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/perf/TimeRunnable.java @@ -0,0 +1,59 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.perf; + +interface TimeRunnable { + void run() throws Exception; +} \ No newline at end of file diff --git a/contrib/src/java/org/jdom/contrib/schema/Schema.java b/contrib/src/java/org/jdom/contrib/schema/Schema.java new file mode 100644 index 0000000..0a2c75e --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/schema/Schema.java @@ -0,0 +1,614 @@ +/*-- + + Copyright (C) 2003-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.schema; + +import java.io.File; +import java.io.InputStream; +import java.io.FileInputStream; +import java.io.Reader; +import java.io.IOException; +import java.util.List; +import java.util.ArrayList; +import java.util.LinkedList; + +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.XMLReader; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.XMLFilterImpl; + +import org.iso_relax.verifier.Verifier; +import org.iso_relax.verifier.VerifierFactory; +import org.iso_relax.verifier.VerifierConfigurationException; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.output.SAXOutputter; +import org.jdom.output.JDOMLocator; + +/** + * The compiled representation of a schema definition capable of + * performing in-memory validation of JDOM documents and elements. + *

+ * This class relies on + * JARV (Java + * API for RELAX Verifiers) and requires an implementation of this + * API at runtime, such as Sun's + * Multi-Schema + * Validator.

+ *

+ * To validate a document against a W3C XML Schema definition:

+ *
+ * import org.jdom.contrib.schema.Schema;
+ *
+ *    String uri = <The URL of the schema document>;
+ *    Document doc = <a JDOM document>;
+ *
+ *    Schema schema = Schema.parse(uri, Schema.W3C_XML_SCHEMA);
+ *    List errors = schema.validate(doc);
+ *    if (errors != null) {
+ *      // Validation errors
+ *      for (Iterator i=errors.iterator(); i.hasNext(); ) {
+ *        ValidationError e = (ValidationError)(i.next());
+ *        System.out.println(e);
+ *      }
+ *    }
+ *    // Else: No error, document is valid.
+ * 
+ *

+ * The current limitations are those of JARV, i.e. no support for + * validating a document against multiple schemas. This can be work around + * for elements (calling validate(Element) on another Schema) but not for + * attributes.

+ * + * @author Laurent Bihanic + */ +public class Schema { + + /** + * Type for W3C XML Schema definitions. + */ + public final static Type W3C_XML_SCHEMA = + new Type("W3C XML Schema", "http://www.w3.org/2001/XMLSchema"); + /** + * Type for RELAX NG schema definitions. + */ + public final static Type RELAX_NG = + new Type("RELAX NG", "http://relaxng.org/ns/structure/0.9"); + /** + * Type for RELAX Core schema definitions. + */ + public final static Type RELAX_CORE = + new Type("RELAX Core", "http://www.xml.gr.jp/xmlns/relaxCore"); + /** + * Type for RELAX Namespace schema definitions. + */ + public final static Type RELAX_NAMESPACE = + new Type("RELAX Namespace", + "http://www.xml.gr.jp/xmlns/relaxNamespace"); + /** + * Type for TREX schema definitions. + */ + public final static Type TREX = + new Type("TREX", "http://www.thaiopensource.com/trex"); + + /** + * The URI of the schema document, if known. + */ + private final String uri; + + /** + * The schema type. + */ + private final Type type; + + /** + * The JARV compiled schema. + */ + private final org.iso_relax.verifier.Schema compiledSchema; + + /** + * Compiles a schema definition. + * + * @param source the SAX input source to read the schema + * definition from. + * @param type the schema type. + * + * @throws JDOMException if the schema document can not be + * parsed according to the specfied type. + * @throws IOException if an I/O error occurred while reading + * the schema document. + */ + private Schema(InputSource source, Type type) + throws JDOMException, IOException { + + if ((source == null) || (type == null)) { + throw new IllegalArgumentException("source/type/compiledSchema"); + } + this.uri = source.getSystemId(); + this.type = type; + + try { + VerifierFactory vf = VerifierFactory.newInstance(type.getLanguage()); + + this.compiledSchema = vf.compileSchema(source); + } + catch (IOException e) { + throw e; + } + catch (Exception e) { + throw new JDOMException("Failed to parse schema \"" + this.uri + + "\": " + e.getMessage(), e); + } + } + + /** + * Returns the location of the schema document, if known. + * + * @return the location of the schema document or + * null if inknown. + */ + public String getURI() { + return this.uri; + } + + /** + * Returns the schema type. + * + * @return the schema type. + */ + public Type getType() { + return this.type; + } + + /** + * Allocates an JARV Verifier object for + * validating against this schema. + * + * @return an JARV Verifier configured with this + * schema. + * + * @throws JDOMException if the verifier allocation failed. + */ + private Verifier newVerifier() throws JDOMException { + try { + return this.compiledSchema.newVerifier(); + } + catch (VerifierConfigurationException e) { + throw new JDOMException( + "Failed to allocate schema verifier: " + e.getMessage(), e); + } + } + + /** + * Validates a JDOM document against this schema. + * + * @param doc the JDOM document to validate. + * + * @return a list of {@link ValidationError} objects or + * null if the document is compliant with + * this schema. + * + * @throws JDOMException if errors were encountered that + * prevented the validation to proceed. + */ + public List validate(Document doc) throws JDOMException { + ValidationErrorHandler errorHandler = new ValidationErrorHandler(); + try { + Verifier verifier = this.newVerifier(); + verifier.setErrorHandler(errorHandler); + + errorHandler.setContentHandler(verifier.getVerifierHandler()); + new SAXOutputter(errorHandler).output(doc); + } + catch (SAXException e) { /* Fatal validation error encountered. */ + } + + // Retrieve validation errors, if any. + return errorHandler.getErrors(); + } + + /** + * Validates a JDOM element against this schema. + * + * @param element the JDOM element to validate. + * + * @return a list of {@link ValidationError} objects or + * null if the element is compliant with + * this schema. + * + * @throws JDOMException if errors were encountered that + * prevented the validation to proceed. + */ + public List validate(Element element) throws JDOMException { + ValidationErrorHandler errorHandler = new ValidationErrorHandler(); + try { + Verifier verifier = this.newVerifier(); + verifier.setErrorHandler(errorHandler); + + List nodes = new ArrayList(); + nodes.add(element); + + errorHandler.setContentHandler(verifier.getVerifierHandler()); + new SAXOutputter(errorHandler).output(nodes); + } + catch (SAXException e) { /* Fatal validation error encountered. */ + } + + // Retrieve validation errors, if any. + return errorHandler.getErrors(); + } + + /** + * Parses a schema definition located at the specified URI + * according to the specified schema type and returns a compiled + * schema object. + * + * @param uri the location of the schema document. + * @param type the schema type. + * + * @return the compiled schema. + * + * @throws JDOMException if the schema document can not be + * parsed according to the specfied type. + * @throws IOException if an I/O error occurred while reading + * the schema document. + */ + public static Schema parse(String uri, Type type) + throws JDOMException, IOException { + return parse(new InputSource(uri), type); + } + + /** + * Parses a schema definition from the specified byte stream + * according to the specified schema type and returns a compiled + * schema object. + * + * @param byteStream the byte stream to read the schema + * definition from. + * @param type the schema type. + * @param uri the location of the schema document + * (optional). + * + * @return the compiled schema. + * + * @throws JDOMException if the schema document can not be + * parsed according to the specfied type. + * @throws IOException if an I/O error occurred while reading + * the schema document. + */ + public static Schema parse(InputStream byteStream, Type type, String uri) + throws JDOMException, IOException { + InputSource source = new InputSource(byteStream); + source.setSystemId(uri); + + return parse(source, type); + } + + /** + * Parses a schema definition from the specified character stream + * according to the specified schema type and returns a compiled + * schema object. + * + * @param reader the character stream to read the schema + * definition from. + * @param type the schema type. + * @param uri the location of the schema document + * (optional). + * + * @return the compiled schema. + * + * @throws JDOMException if the schema document can not be + * parsed according to the specfied type. + * @throws IOException if an I/O error occurred while reading + * the schema document. + */ + public static Schema parse(Reader reader, Type type, String uri) + throws JDOMException, IOException { + InputSource source = new InputSource(reader); + source.setSystemId(uri); + + return parse(source, type); + } + + /** + * Parses a schema definition from the specified file + * according to the specified schema type and returns a compiled + * schema object. + * + * @param file the file to read the schema definition from. + * @param type the schema type. + * + * @return the compiled schema. + * + * @throws JDOMException if the schema document can not be + * parsed according to the specfied type. + * @throws IOException if an I/O error occurred while reading + * the schema document. + */ + public static Schema parse(File file, Type type) + throws JDOMException, IOException { + InputSource source = new InputSource(new FileInputStream(file)); + source.setSystemId(file.getAbsolutePath()); + + return parse(source, type); + } + + /** + * Parses a schema definition from the specified SAX input source + * according to the specified schema type and returns a compiled + * schema object. + * + * @param source the SAX inout source to read the schema + * definition from. + * @param type the schema type. + * + * @return the compiled schema. + * + * @throws JDOMException if the schema document can not be + * parsed according to the specfied type. + * @throws IOException if an I/O error occurred while reading + * the schema document. + */ + public static Schema parse(InputSource source, Type type) + throws JDOMException, IOException { + return new Schema(source, type); + } + + + /** + * A SAX XML filter implementation to capture the document locator + * and make all validation errors and warnings available once the + * validation is complete. + */ + private static final class ValidationErrorHandler extends XMLFilterImpl { + /** The list of validation errors. */ + private List errors = new LinkedList(); + /** The JDOM locator object provided by SAXOutputter. */ + private JDOMLocator locator = null; + + /** + * Constructs a new ValidationErrorHandler XML filter with no + * parent. + */ + public ValidationErrorHandler() { + super(); + } + + /** + * Constructs a new ValidationErrorHandler XML filter with the + * specified parent. + * + * @param parent the parent XMLReader or XMLFilter. + */ + @SuppressWarnings("unused") + public ValidationErrorHandler(XMLReader parent) { + super(parent); + } + + /** + * Returns the list of validation errors reported during + * document validation. + * + * @return the list of validation errors or null + * if the document is valid. + */ + public List getErrors() { + return (this.errors.size() == 0) ? null : this.errors; + } + + /** + * Returns the JDOM node currently being ouputted by + * SAXOuputter. + * + * @return the current JDOM node. + */ + private Object getCurrentNode() { + return (this.locator != null) ? this.locator.getNode() : null; + } + + /** + * [ContentHandler interface support] Sets the locator + * object for locating the origin of SAX document events. + * + * @param locator an object that can return the location of + * any SAX document event. + */ + @Override + public void setDocumentLocator(Locator locator) { + if (locator instanceof JDOMLocator) { + this.locator = (JDOMLocator) locator; + } + } + + /** + * [ErrorHandler interface support] Receives + * notification of a non-recoverable error. + * + * @param e the error information encapsulated in a SAX + * parse exception. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void fatalError(SAXParseException e) throws SAXException { + this.errors.add(new ValidationError(ValidationError.FATAL, + e.getMessage(), this.getCurrentNode())); + throw e; + } + + /** + * [ErrorHandler interface support] Receives + * notification of a recoverable error. + * + * @param e the error information encapsulated in a SAX + * parse exception. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void error(SAXParseException e) throws SAXException { + this.errors.add(new ValidationError(ValidationError.ERROR, + e.getMessage(), this.getCurrentNode())); + } + + /** + * [ErrorHandler interface support] Receives + * notification of a warning. + * + * @param e the warning information encapsulated in a SAX + * parse exception. + * + * @throws SAXException any SAX exception, possibly wrapping + * another exception. + */ + @Override + public void warning(SAXParseException e) throws SAXException { + this.errors.add(new ValidationError(ValidationError.WARNING, + e.getMessage(), this.getCurrentNode())); + } + } + + + /** + * Class to support type-safe enumeration design pattern to + * represent schema types + */ + public static final class Type { + /** Schema type name. */ + private final String name; + /** JARV schema type identifier. */ + private final String language; + + /** + * Type constructor, private on purpose. + * + * @param name the schema type printable name. + * @param language the unique identifier for the schema + * type (URI). + */ + protected Type(String name, String language) { + this.name = name; + this.language = language; + } + + /** + * Returns the printable name of this schema type. + * + * @return the schema type name. + */ + public String getName() { + return this.name; + } + + /** + * Returns the URI that uniquemy identifies this schema type. + * + * @return the schema type identifier. + */ + public String getLanguage() { + return this.language; + } + + /** + * Returns a unique identifier for this type. + * + * @return a unique identifier for this type. + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return this.language.hashCode(); + } + + /** + * Returns a string representation of this type suitable for + * debugging and diagnosis. + * + * @return a string representation of this type. + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return this.language; + } + + /** + * Tests for type equality. This is only necessary to handle + * cases where two Type objects are loaded by + * different class loaders. + * + * @param o the object compared for equality to this type. + * + * @return true if and only if o + * represents the same type as this object. + * + * @see java.lang.Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + return ((o == this) || + ((o != null) && (this.hashCode() == o.hashCode()) && + (this.getClass().getName().equals(o.getClass().getName())))); + } + } +} + diff --git a/contrib/src/java/org/jdom/contrib/schema/ValidationError.java b/contrib/src/java/org/jdom/contrib/schema/ValidationError.java new file mode 100644 index 0000000..a7b4883 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/schema/ValidationError.java @@ -0,0 +1,232 @@ +/*-- + + Copyright (C) 2003-2004 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.schema; + +/** + * A ValidationError object encapsulates a schema validation error or + * warning. + * + * @author Laurent Bihanic + */ +public class ValidationError { + /** The severity for warnings. */ + public final static Severity WARNING = new Severity(0); + /** The severity for recoverable validation errors. */ + public final static Severity ERROR = new Severity(1); + /** The severity for non-recoverable validation errors. */ + public final static Severity FATAL = new Severity(2); + + /** + * The error severity. + */ + private final Severity severity; + + /** + * The detailed error message. + */ + private final String message; + + /** + * The JDOM node that caused the error. + */ + private final Object node; + + /** + * Creates a new validation error. + * + * @param severity the error severity. + * @param message the detailed error message. + */ + public ValidationError(Severity severity, String message) { + this(severity, message, null); + } + + /** + * Creates a new validation error. + * + * @param severity the error severity. + * @param message the detailed error message. + * @param node the JDOM node that caused the error. + */ + public ValidationError(Severity severity, String message, Object node) { + this.severity = severity; + this.message = message; + this.node = node; + } + + /** + * Returns the severity of this error. + * + * @return the severity of this error. + */ + public Severity getSeverity() { + return this.severity; + } + + /** + * Returns the detailed error message. + * + * @return the detailed error message. + */ + public String getMessage() { + return this.message; + } + + /** + * Returns the JDOM node that caused the error. + * + * @return the JDOM node that caused the error. + */ + public Object getNode() { + return this.node; + } + + /** + * Returns a string representation of this error suitable for + * debugging and diagnosis. + * + * @return a string representation of this error. + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + + buf.append('['); + if (this.severity == WARNING) { + buf.append("WARNING"); + } + else if (this.severity == ERROR) { + buf.append("ERROR"); + } + else if (this.severity == FATAL) { + buf.append("FATAL"); + } + buf.append("] message: \"").append(this.getMessage()); + + if (this.getNode() != null) { + buf.append("\", location: \"").append(this.getNode().toString()); + } + buf.append("\""); + + return buf.toString(); + } + + + /** + * Class to support type-safe enumeration design pattern to + * represent severity levels of validation errors. + */ + public static final class Severity { + /** The severity of the error. */ + private final int level; + + /** + * Severity constructor, private on purpose. + * + * @param level the severity level. + */ + protected Severity(int level) { + this.level = level; + } + + /** + * Returns a unique identifier for this severity. + * + * @return a unique identifier for this severity. + * + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return this.level; + } + + /** + * Returns a string representation of this severity suitable + * for debugging and diagnosis. + * + * @return a string representation of this severity. + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "[" + this.getClass().getName() + "] " + this.level; + } + + /** + * Tests for severity equality. This is only necessary to + * handle cases where two Type objects are loaded + * by different class loaders. + * + * @param o the object compared for equality to this + * severity. + * + * @return true if and only if o + * represents the same severity as this object. + * + * @see java.lang.Object#equals(Object) + */ + @Override + public boolean equals(Object o) { + return ((o == this) || + ((o != null) && (this.hashCode() == o.hashCode()) && + (this.getClass().getName().equals(o.getClass().getName())))); + } + } +} + diff --git a/contrib/src/java/org/jdom/contrib/verifier/VerifierBuilder.java b/contrib/src/java/org/jdom/contrib/verifier/VerifierBuilder.java new file mode 100644 index 0000000..f72d3c3 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/verifier/VerifierBuilder.java @@ -0,0 +1,721 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.verifier; + +import org.jdom.Verifier; + +/** + * A utility class to build the data component of the main + * org.jdom.Verifier class. This class contains all the character + * identification/classification routines. + *

+ * This class is based on the content of the main Verifier.java class + * prior to this optimization. + * + * @author Brett McLaughlin + * @author Elliotte Rusty Harold + * @author Jason Hunter + * @author Bradley S. Huffman + * @author Rolf Lear + */ +final public class VerifierBuilder { + + /** + * Ensure instantation cannot occur. + */ + private VerifierBuilder() { } + + + private static final int charcnt = Character.MAX_VALUE + 1; + private static final byte maskxmlcharacter = 1 << 0; + private static final byte maskxmlletter = 1 << 1; + private static final byte maskxmlstart = 1 << 2; + private static final byte maskxmlnamecharacter = 1 << 3; + private static final byte maskxmldigit = 1 << 4; + private static final byte maskxmlcombining = 1 << 5; + private static final byte maskuricharacter = 1 << 6; + + @SuppressWarnings("javadoc") + public static void main(String[] args) { + // populate the flags array. + final byte[] flags = new byte[charcnt]; + + for (int i = 0; i < charcnt; i++) { + if (isXMLCharacter(i)) { + flags[i] |= maskxmlcharacter; + } + + final char c = (char)i; + + if (isXMLLetter(c)) { + flags[i] |= maskxmlletter; + } + if (isXMLNameCharacter(c)) { + flags[i] |= maskxmlnamecharacter; + } + if (isXMLNameStartCharacter(c)) { + flags[i] |= maskxmlstart; + } + if (isXMLDigit(c)) { + flags[i] |= maskxmldigit; + } + if (isXMLCombiningChar(c)) { + flags[i] |= maskxmlcombining; + } + if (isURICharacter(c)) { + flags[i] |= maskuricharacter; + } + + if (flags[i] != 0) { + if ((flags[i] & maskxmlcharacter) == 0) { + // for performance reasons, the testing of CharacterData + // in the final Verifier code does not use the bit-mask for it, + // but rather just checks for a non-zero flag. It does not need to + // check the actual bit because there are no characters flagged for + // any other role (name chars, uri's, etc.) that are not also a pure + // subset of the Character Data set. + throw new IllegalStateException("Flagged non-xmlchar '" + (char)i + "'."); + } + } + } + + // OK, now 'condense' the flags array to something usable. + byte[] vals = new byte[flags.length]; + int[] lens = new int[flags.length]; + int index = 0; + byte val = flags[0]; + int cnt = 0; + + for (int i = 0; i < flags.length; i++) { + if (flags[i] == val) { + cnt++; + } else { + vals[index] = val; + lens[index] = cnt; + val = flags[i]; + cnt = 1; + index++; + } + } + vals[index] = val; + lens[index] = cnt; + index++; + + int ci = 0; + for (int i = 0; i < index; i++) { + int l = lens[i]; + final byte v = vals[i]; + while (--l >= 0) { + if (flags[ci] != v) { + throw new IllegalStateException(String.format( + "Failed to calculate byte 0x%02x at index %d. Calculated 0x%02x instead.", + flags[ci], ci, v)); + } + ci++; + } + } + + System.out.println("There are " + index + " transitions."); + + StringBuilder sbval = new StringBuilder(); + StringBuilder sblen = new StringBuilder(); + + sbval.append("private static final byte[] VALCONST = new byte[] {"); + sblen.append("private static final int [] LENCONST = new int [] {"); + + for (int i = 0; i < index; i++) { + if (i > 0) { + sbval.append(", "); + sblen.append(", "); + } + if ((i % 8) == 0) { + sbval.append("\n "); + sblen.append("\n "); + } + if ((vals[i] & (byte)0x80) != 0) { + sbval.append("(byte)"); + } + sbval.append(String.format("0x%02x", vals[i])); + sblen.append(String.format("%5d", lens[i])); + } + + sbval.append("};\n"); + sblen.append("};\n"); + + System.out.println(sbval.toString()); + System.out.println(sblen.toString()); + + } + + + /** + *

+ * This is a utility function for determining whether a specified + * Unicode character is a hexadecimal digit as defined in RFC 2396; + * that is, one of the ASCII characters 0-9, a-f, or A-F. + *

+ * + * @param c to check for hex digit. + * @return true if it's allowed, false otherwise. + */ + public static boolean isHexDigit(final char c) { + + // I suspect most characters passed to this method will be + // correct hexadecimal digits, so I test for the true cases + // first. If this proves to be a performance bottleneck + // a switch statement or lookup table + // might optimize this. + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'F') return true; + if (c >= 'a' && c <= 'f') return true; + + return false; + } + + /** + *

+ * This is a utility function for determining whether + * a specified Unicode character is legal in URI references + * as determined by RFC 2396. + *

+ * + * @param c char to check for URI reference compliance. + * @return true if it's allowed, false otherwise. + */ + public static boolean isURICharacter(final char c) { + if (c >= 'a' && c <= 'z') return true; + if (c >= 'A' && c <= 'Z') return true; + if (c >= '0' && c <= '9') return true; + if (c == '/') return true; + if (c == '-') return true; + if (c == '.') return true; + if (c == '?') return true; + if (c == ':') return true; + if (c == '@') return true; + if (c == '&') return true; + if (c == '=') return true; + if (c == '+') return true; + if (c == '$') return true; + if (c == ',') return true; + if (c == '%') return true; + + if (c == '_') return true; + if (c == '!') return true; + if (c == '~') return true; + if (c == '*') return true; + if (c == '\'') return true; + if (c == '(') return true; + if (c == ')') return true; + return false; + } + + /** + * This is a utility function for determining whether a specified + * character is a character according to production 2 of the + * XML 1.0 specification. + * + * @param c char to check for XML compliance + * @return boolean true if it's a character, + * false otherwise + */ + public static boolean isXMLCharacter(final int c) { + + if (c == '\n') return true; + if (c == '\r') return true; + if (c == '\t') return true; + + if (c < 0x20) return false; if (c <= 0xD7FF) return true; + if (c < 0xE000) return false; if (c <= 0xFFFD) return true; + if (c < 0x10000) return false; if (c <= 0x10FFFF) return true; + + return false; + } + + + /** + * This is a utility function for determining whether a specified + * character is a name character according to production 4 of the + * XML 1.0 specification. + * + * @param c char to check for XML name compliance. + * @return boolean true if it's a name character, + * false otherwise. + */ + public static boolean isXMLNameCharacter(final char c) { + + // remove check for || c == ':' + // JDOM Attributes and Elements cannot start with ':' since JDOM + // seperates the prefix from the name. + // we do not want ':' in the bitmask, instead we add it later. + + return (isXMLLetter(c) || isXMLDigit(c) || c == '.' || c == '-' + || c == '_' || isXMLCombiningChar(c) + || Verifier.isXMLExtender(c)); + } + + /** + * This is a utility function for determining whether a specified + * character is a legal name start character according to production 5 + * of the XML 1.0 specification. This production does allow names + * to begin with colons which the Namespaces in XML Recommendation + * disallows. + * + * @param c char to check for XML name start compliance. + * @return boolean true if it's a name start character, + * false otherwise. + */ + public static boolean isXMLNameStartCharacter(final char c) { + + // remove check for || c == ':' + // JDOM Attributes and Elements cannot start with ':' since JDOM + // seperates the prefix from the name. + // we do not want ':' in the bitmask, instead we add it later. + + return (isXMLLetter(c) || c == '_'); + + } + + /** + * This is a utility function for determining whether a specified character + * is a letter according to production 84 of the XML 1.0 specification. + * + * @param c char to check for XML name compliance. + * @return String true if it's a letter, false otherwise. + */ + public static boolean isXMLLetter(final char c) { + // Note that order is very important here. The search proceeds + // from lowest to highest values, so that no searching occurs + // above the character's value. BTW, the first line is equivalent to: + // if (c >= 0x0041 && c <= 0x005A) return true; + + if (c < 0x0041) return false; if (c <= 0x005a) return true; + if (c < 0x0061) return false; if (c <= 0x007A) return true; + if (c < 0x00C0) return false; if (c <= 0x00D6) return true; + if (c < 0x00D8) return false; if (c <= 0x00F6) return true; + if (c < 0x00F8) return false; if (c <= 0x00FF) return true; + if (c < 0x0100) return false; if (c <= 0x0131) return true; + if (c < 0x0134) return false; if (c <= 0x013E) return true; + if (c < 0x0141) return false; if (c <= 0x0148) return true; + if (c < 0x014A) return false; if (c <= 0x017E) return true; + if (c < 0x0180) return false; if (c <= 0x01C3) return true; + if (c < 0x01CD) return false; if (c <= 0x01F0) return true; + if (c < 0x01F4) return false; if (c <= 0x01F5) return true; + if (c < 0x01FA) return false; if (c <= 0x0217) return true; + if (c < 0x0250) return false; if (c <= 0x02A8) return true; + if (c < 0x02BB) return false; if (c <= 0x02C1) return true; + if (c == 0x0386) return true; + if (c < 0x0388) return false; if (c <= 0x038A) return true; + if (c == 0x038C) return true; + if (c < 0x038E) return false; if (c <= 0x03A1) return true; + if (c < 0x03A3) return false; if (c <= 0x03CE) return true; + if (c < 0x03D0) return false; if (c <= 0x03D6) return true; + if (c == 0x03DA) return true; + if (c == 0x03DC) return true; + if (c == 0x03DE) return true; + if (c == 0x03E0) return true; + if (c < 0x03E2) return false; if (c <= 0x03F3) return true; + if (c < 0x0401) return false; if (c <= 0x040C) return true; + if (c < 0x040E) return false; if (c <= 0x044F) return true; + if (c < 0x0451) return false; if (c <= 0x045C) return true; + if (c < 0x045E) return false; if (c <= 0x0481) return true; + if (c < 0x0490) return false; if (c <= 0x04C4) return true; + if (c < 0x04C7) return false; if (c <= 0x04C8) return true; + if (c < 0x04CB) return false; if (c <= 0x04CC) return true; + if (c < 0x04D0) return false; if (c <= 0x04EB) return true; + if (c < 0x04EE) return false; if (c <= 0x04F5) return true; + if (c < 0x04F8) return false; if (c <= 0x04F9) return true; + if (c < 0x0531) return false; if (c <= 0x0556) return true; + if (c == 0x0559) return true; + if (c < 0x0561) return false; if (c <= 0x0586) return true; + if (c < 0x05D0) return false; if (c <= 0x05EA) return true; + if (c < 0x05F0) return false; if (c <= 0x05F2) return true; + if (c < 0x0621) return false; if (c <= 0x063A) return true; + if (c < 0x0641) return false; if (c <= 0x064A) return true; + if (c < 0x0671) return false; if (c <= 0x06B7) return true; + if (c < 0x06BA) return false; if (c <= 0x06BE) return true; + if (c < 0x06C0) return false; if (c <= 0x06CE) return true; + if (c < 0x06D0) return false; if (c <= 0x06D3) return true; + if (c == 0x06D5) return true; + if (c < 0x06E5) return false; if (c <= 0x06E6) return true; + if (c < 0x0905) return false; if (c <= 0x0939) return true; + if (c == 0x093D) return true; + if (c < 0x0958) return false; if (c <= 0x0961) return true; + if (c < 0x0985) return false; if (c <= 0x098C) return true; + if (c < 0x098F) return false; if (c <= 0x0990) return true; + if (c < 0x0993) return false; if (c <= 0x09A8) return true; + if (c < 0x09AA) return false; if (c <= 0x09B0) return true; + if (c == 0x09B2) return true; + if (c < 0x09B6) return false; if (c <= 0x09B9) return true; + if (c < 0x09DC) return false; if (c <= 0x09DD) return true; + if (c < 0x09DF) return false; if (c <= 0x09E1) return true; + if (c < 0x09F0) return false; if (c <= 0x09F1) return true; + if (c < 0x0A05) return false; if (c <= 0x0A0A) return true; + if (c < 0x0A0F) return false; if (c <= 0x0A10) return true; + if (c < 0x0A13) return false; if (c <= 0x0A28) return true; + if (c < 0x0A2A) return false; if (c <= 0x0A30) return true; + if (c < 0x0A32) return false; if (c <= 0x0A33) return true; + if (c < 0x0A35) return false; if (c <= 0x0A36) return true; + if (c < 0x0A38) return false; if (c <= 0x0A39) return true; + if (c < 0x0A59) return false; if (c <= 0x0A5C) return true; + if (c == 0x0A5E) return true; + if (c < 0x0A72) return false; if (c <= 0x0A74) return true; + if (c < 0x0A85) return false; if (c <= 0x0A8B) return true; + if (c == 0x0A8D) return true; + if (c < 0x0A8F) return false; if (c <= 0x0A91) return true; + if (c < 0x0A93) return false; if (c <= 0x0AA8) return true; + if (c < 0x0AAA) return false; if (c <= 0x0AB0) return true; + if (c < 0x0AB2) return false; if (c <= 0x0AB3) return true; + if (c < 0x0AB5) return false; if (c <= 0x0AB9) return true; + if (c == 0x0ABD) return true; + if (c == 0x0AE0) return true; + if (c < 0x0B05) return false; if (c <= 0x0B0C) return true; + if (c < 0x0B0F) return false; if (c <= 0x0B10) return true; + if (c < 0x0B13) return false; if (c <= 0x0B28) return true; + if (c < 0x0B2A) return false; if (c <= 0x0B30) return true; + if (c < 0x0B32) return false; if (c <= 0x0B33) return true; + if (c < 0x0B36) return false; if (c <= 0x0B39) return true; + if (c == 0x0B3D) return true; + if (c < 0x0B5C) return false; if (c <= 0x0B5D) return true; + if (c < 0x0B5F) return false; if (c <= 0x0B61) return true; + if (c < 0x0B85) return false; if (c <= 0x0B8A) return true; + if (c < 0x0B8E) return false; if (c <= 0x0B90) return true; + if (c < 0x0B92) return false; if (c <= 0x0B95) return true; + if (c < 0x0B99) return false; if (c <= 0x0B9A) return true; + if (c == 0x0B9C) return true; + if (c < 0x0B9E) return false; if (c <= 0x0B9F) return true; + if (c < 0x0BA3) return false; if (c <= 0x0BA4) return true; + if (c < 0x0BA8) return false; if (c <= 0x0BAA) return true; + if (c < 0x0BAE) return false; if (c <= 0x0BB5) return true; + if (c < 0x0BB7) return false; if (c <= 0x0BB9) return true; + if (c < 0x0C05) return false; if (c <= 0x0C0C) return true; + if (c < 0x0C0E) return false; if (c <= 0x0C10) return true; + if (c < 0x0C12) return false; if (c <= 0x0C28) return true; + if (c < 0x0C2A) return false; if (c <= 0x0C33) return true; + if (c < 0x0C35) return false; if (c <= 0x0C39) return true; + if (c < 0x0C60) return false; if (c <= 0x0C61) return true; + if (c < 0x0C85) return false; if (c <= 0x0C8C) return true; + if (c < 0x0C8E) return false; if (c <= 0x0C90) return true; + if (c < 0x0C92) return false; if (c <= 0x0CA8) return true; + if (c < 0x0CAA) return false; if (c <= 0x0CB3) return true; + if (c < 0x0CB5) return false; if (c <= 0x0CB9) return true; + if (c == 0x0CDE) return true; + if (c < 0x0CE0) return false; if (c <= 0x0CE1) return true; + if (c < 0x0D05) return false; if (c <= 0x0D0C) return true; + if (c < 0x0D0E) return false; if (c <= 0x0D10) return true; + if (c < 0x0D12) return false; if (c <= 0x0D28) return true; + if (c < 0x0D2A) return false; if (c <= 0x0D39) return true; + if (c < 0x0D60) return false; if (c <= 0x0D61) return true; + if (c < 0x0E01) return false; if (c <= 0x0E2E) return true; + if (c == 0x0E30) return true; + if (c < 0x0E32) return false; if (c <= 0x0E33) return true; + if (c < 0x0E40) return false; if (c <= 0x0E45) return true; + if (c < 0x0E81) return false; if (c <= 0x0E82) return true; + if (c == 0x0E84) return true; + if (c < 0x0E87) return false; if (c <= 0x0E88) return true; + if (c == 0x0E8A) return true; + if (c == 0x0E8D) return true; + if (c < 0x0E94) return false; if (c <= 0x0E97) return true; + if (c < 0x0E99) return false; if (c <= 0x0E9F) return true; + if (c < 0x0EA1) return false; if (c <= 0x0EA3) return true; + if (c == 0x0EA5) return true; + if (c == 0x0EA7) return true; + if (c < 0x0EAA) return false; if (c <= 0x0EAB) return true; + if (c < 0x0EAD) return false; if (c <= 0x0EAE) return true; + if (c == 0x0EB0) return true; + if (c < 0x0EB2) return false; if (c <= 0x0EB3) return true; + if (c == 0x0EBD) return true; + if (c < 0x0EC0) return false; if (c <= 0x0EC4) return true; + if (c < 0x0F40) return false; if (c <= 0x0F47) return true; + if (c < 0x0F49) return false; if (c <= 0x0F69) return true; + if (c < 0x10A0) return false; if (c <= 0x10C5) return true; + if (c < 0x10D0) return false; if (c <= 0x10F6) return true; + if (c == 0x1100) return true; + if (c < 0x1102) return false; if (c <= 0x1103) return true; + if (c < 0x1105) return false; if (c <= 0x1107) return true; + if (c == 0x1109) return true; + if (c < 0x110B) return false; if (c <= 0x110C) return true; + if (c < 0x110E) return false; if (c <= 0x1112) return true; + if (c == 0x113C) return true; + if (c == 0x113E) return true; + if (c == 0x1140) return true; + if (c == 0x114C) return true; + if (c == 0x114E) return true; + if (c == 0x1150) return true; + if (c < 0x1154) return false; if (c <= 0x1155) return true; + if (c == 0x1159) return true; + if (c < 0x115F) return false; if (c <= 0x1161) return true; + if (c == 0x1163) return true; + if (c == 0x1165) return true; + if (c == 0x1167) return true; + if (c == 0x1169) return true; + if (c < 0x116D) return false; if (c <= 0x116E) return true; + if (c < 0x1172) return false; if (c <= 0x1173) return true; + if (c == 0x1175) return true; + if (c == 0x119E) return true; + if (c == 0x11A8) return true; + if (c == 0x11AB) return true; + if (c < 0x11AE) return false; if (c <= 0x11AF) return true; + if (c < 0x11B7) return false; if (c <= 0x11B8) return true; + if (c == 0x11BA) return true; + if (c < 0x11BC) return false; if (c <= 0x11C2) return true; + if (c == 0x11EB) return true; + if (c == 0x11F0) return true; + if (c == 0x11F9) return true; + if (c < 0x1E00) return false; if (c <= 0x1E9B) return true; + if (c < 0x1EA0) return false; if (c <= 0x1EF9) return true; + if (c < 0x1F00) return false; if (c <= 0x1F15) return true; + if (c < 0x1F18) return false; if (c <= 0x1F1D) return true; + if (c < 0x1F20) return false; if (c <= 0x1F45) return true; + if (c < 0x1F48) return false; if (c <= 0x1F4D) return true; + if (c < 0x1F50) return false; if (c <= 0x1F57) return true; + if (c == 0x1F59) return true; + if (c == 0x1F5B) return true; + if (c == 0x1F5D) return true; + if (c < 0x1F5F) return false; if (c <= 0x1F7D) return true; + if (c < 0x1F80) return false; if (c <= 0x1FB4) return true; + if (c < 0x1FB6) return false; if (c <= 0x1FBC) return true; + if (c == 0x1FBE) return true; + if (c < 0x1FC2) return false; if (c <= 0x1FC4) return true; + if (c < 0x1FC6) return false; if (c <= 0x1FCC) return true; + if (c < 0x1FD0) return false; if (c <= 0x1FD3) return true; + if (c < 0x1FD6) return false; if (c <= 0x1FDB) return true; + if (c < 0x1FE0) return false; if (c <= 0x1FEC) return true; + if (c < 0x1FF2) return false; if (c <= 0x1FF4) return true; + if (c < 0x1FF6) return false; if (c <= 0x1FFC) return true; + if (c == 0x2126) return true; + if (c < 0x212A) return false; if (c <= 0x212B) return true; + if (c == 0x212E) return true; + if (c < 0x2180) return false; if (c <= 0x2182) return true; + if (c == 0x3007) return true; // ideographic + if (c < 0x3021) return false; if (c <= 0x3029) return true; // ideo + if (c < 0x3041) return false; if (c <= 0x3094) return true; + if (c < 0x30A1) return false; if (c <= 0x30FA) return true; + if (c < 0x3105) return false; if (c <= 0x312C) return true; + if (c < 0x4E00) return false; if (c <= 0x9FA5) return true; // ideo + if (c < 0xAC00) return false; if (c <= 0xD7A3) return true; + + return false; + + } + + /** + * This is a utility function for determining whether a specified character + * is a combining character according to production 87 + * of the XML 1.0 specification. + * + * @param c char to check. + * @return boolean true if it's a combining character, + * false otherwise. + */ + public static boolean isXMLCombiningChar(final char c) { + // CombiningChar + if (c < 0x0300) return false; if (c <= 0x0345) return true; + if (c < 0x0360) return false; if (c <= 0x0361) return true; + if (c < 0x0483) return false; if (c <= 0x0486) return true; + if (c < 0x0591) return false; if (c <= 0x05A1) return true; + + if (c < 0x05A3) return false; if (c <= 0x05B9) return true; + if (c < 0x05BB) return false; if (c <= 0x05BD) return true; + if (c == 0x05BF) return true; + if (c < 0x05C1) return false; if (c <= 0x05C2) return true; + + if (c == 0x05C4) return true; + if (c < 0x064B) return false; if (c <= 0x0652) return true; + if (c == 0x0670) return true; + if (c < 0x06D6) return false; if (c <= 0x06DC) return true; + + if (c < 0x06DD) return false; if (c <= 0x06DF) return true; + if (c < 0x06E0) return false; if (c <= 0x06E4) return true; + if (c < 0x06E7) return false; if (c <= 0x06E8) return true; + + if (c < 0x06EA) return false; if (c <= 0x06ED) return true; + if (c < 0x0901) return false; if (c <= 0x0903) return true; + if (c == 0x093C) return true; + if (c < 0x093E) return false; if (c <= 0x094C) return true; + + if (c == 0x094D) return true; + if (c < 0x0951) return false; if (c <= 0x0954) return true; + if (c < 0x0962) return false; if (c <= 0x0963) return true; + if (c < 0x0981) return false; if (c <= 0x0983) return true; + + if (c == 0x09BC) return true; + if (c == 0x09BE) return true; + if (c == 0x09BF) return true; + if (c < 0x09C0) return false; if (c <= 0x09C4) return true; + if (c < 0x09C7) return false; if (c <= 0x09C8) return true; + + if (c < 0x09CB) return false; if (c <= 0x09CD) return true; + if (c == 0x09D7) return true; + if (c < 0x09E2) return false; if (c <= 0x09E3) return true; + if (c == 0x0A02) return true; + if (c == 0x0A3C) return true; + + if (c == 0x0A3E) return true; + if (c == 0x0A3F) return true; + if (c < 0x0A40) return false; if (c <= 0x0A42) return true; + if (c < 0x0A47) return false; if (c <= 0x0A48) return true; + + if (c < 0x0A4B) return false; if (c <= 0x0A4D) return true; + if (c < 0x0A70) return false; if (c <= 0x0A71) return true; + if (c < 0x0A81) return false; if (c <= 0x0A83) return true; + if (c == 0x0ABC) return true; + + if (c < 0x0ABE) return false; if (c <= 0x0AC5) return true; + if (c < 0x0AC7) return false; if (c <= 0x0AC9) return true; + if (c < 0x0ACB) return false; if (c <= 0x0ACD) return true; + + if (c < 0x0B01) return false; if (c <= 0x0B03) return true; + if (c == 0x0B3C) return true; + if (c < 0x0B3E) return false; if (c <= 0x0B43) return true; + if (c < 0x0B47) return false; if (c <= 0x0B48) return true; + + if (c < 0x0B4B) return false; if (c <= 0x0B4D) return true; + if (c < 0x0B56) return false; if (c <= 0x0B57) return true; + if (c < 0x0B82) return false; if (c <= 0x0B83) return true; + + if (c < 0x0BBE) return false; if (c <= 0x0BC2) return true; + if (c < 0x0BC6) return false; if (c <= 0x0BC8) return true; + if (c < 0x0BCA) return false; if (c <= 0x0BCD) return true; + if (c == 0x0BD7) return true; + + if (c < 0x0C01) return false; if (c <= 0x0C03) return true; + if (c < 0x0C3E) return false; if (c <= 0x0C44) return true; + if (c < 0x0C46) return false; if (c <= 0x0C48) return true; + + if (c < 0x0C4A) return false; if (c <= 0x0C4D) return true; + if (c < 0x0C55) return false; if (c <= 0x0C56) return true; + if (c < 0x0C82) return false; if (c <= 0x0C83) return true; + + if (c < 0x0CBE) return false; if (c <= 0x0CC4) return true; + if (c < 0x0CC6) return false; if (c <= 0x0CC8) return true; + if (c < 0x0CCA) return false; if (c <= 0x0CCD) return true; + + if (c < 0x0CD5) return false; if (c <= 0x0CD6) return true; + if (c < 0x0D02) return false; if (c <= 0x0D03) return true; + if (c < 0x0D3E) return false; if (c <= 0x0D43) return true; + + if (c < 0x0D46) return false; if (c <= 0x0D48) return true; + if (c < 0x0D4A) return false; if (c <= 0x0D4D) return true; + if (c == 0x0D57) return true; + if (c == 0x0E31) return true; + + if (c < 0x0E34) return false; if (c <= 0x0E3A) return true; + if (c < 0x0E47) return false; if (c <= 0x0E4E) return true; + if (c == 0x0EB1) return true; + if (c < 0x0EB4) return false; if (c <= 0x0EB9) return true; + + if (c < 0x0EBB) return false; if (c <= 0x0EBC) return true; + if (c < 0x0EC8) return false; if (c <= 0x0ECD) return true; + if (c < 0x0F18) return false; if (c <= 0x0F19) return true; + if (c == 0x0F35) return true; + + if (c == 0x0F37) return true; + if (c == 0x0F39) return true; + if (c == 0x0F3E) return true; + if (c == 0x0F3F) return true; + if (c < 0x0F71) return false; if (c <= 0x0F84) return true; + + if (c < 0x0F86) return false; if (c <= 0x0F8B) return true; + if (c < 0x0F90) return false; if (c <= 0x0F95) return true; + if (c == 0x0F97) return true; + if (c < 0x0F99) return false; if (c <= 0x0FAD) return true; + + if (c < 0x0FB1) return false; if (c <= 0x0FB7) return true; + if (c == 0x0FB9) return true; + if (c < 0x20D0) return false; if (c <= 0x20DC) return true; + if (c == 0x20E1) return true; + + if (c < 0x302A) return false; if (c <= 0x302F) return true; + if (c == 0x3099) return true; + if (c == 0x309A) return true; + + return false; + + } + + /** + * This is a utility function for determining whether a specified + * Unicode character + * is a digit according to production 88 of the XML 1.0 specification. + * + * @param c char to check for XML digit compliance + * @return boolean true if it's a digit, false otherwise + */ + public static boolean isXMLDigit(final char c) { + + if (c < 0x0030) return false; if (c <= 0x0039) return true; + if (c < 0x0660) return false; if (c <= 0x0669) return true; + if (c < 0x06F0) return false; if (c <= 0x06F9) return true; + if (c < 0x0966) return false; if (c <= 0x096F) return true; + + if (c < 0x09E6) return false; if (c <= 0x09EF) return true; + if (c < 0x0A66) return false; if (c <= 0x0A6F) return true; + if (c < 0x0AE6) return false; if (c <= 0x0AEF) return true; + + if (c < 0x0B66) return false; if (c <= 0x0B6F) return true; + if (c < 0x0BE7) return false; if (c <= 0x0BEF) return true; + if (c < 0x0C66) return false; if (c <= 0x0C6F) return true; + + if (c < 0x0CE6) return false; if (c <= 0x0CEF) return true; + if (c < 0x0D66) return false; if (c <= 0x0D6F) return true; + if (c < 0x0E50) return false; if (c <= 0x0E59) return true; + + if (c < 0x0ED0) return false; if (c <= 0x0ED9) return true; + if (c < 0x0F20) return false; if (c <= 0x0F29) return true; + + return false; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/xpath/java/JavaXPathExpression.java b/contrib/src/java/org/jdom/contrib/xpath/java/JavaXPathExpression.java new file mode 100644 index 0000000..fc3281c --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/xpath/java/JavaXPathExpression.java @@ -0,0 +1,272 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.xpath.java; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.xml.namespace.NamespaceContext; +import javax.xml.namespace.QName; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathVariableResolver; + +import org.w3c.dom.Attr; +import org.w3c.dom.NodeList; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMConstants; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.contrib.dom.DOM; +import org.jdom.contrib.dom.Wrapper; +import org.jdom.filter2.Filter; +import org.jdom.internal.ArrayCopy; +import org.jdom.xpath.util.AbstractXPathCompiled; + +/** + * An XPathExpression that uses the native Java5 javax.xml.xpath mechanisms + * to implement XPath. + * + * @author Rolf Lear + * + * @param the type of the coerced results. + */ +class JavaXPathExpression extends AbstractXPathCompiled + implements XPathVariableResolver, NamespaceContext { + + private static final Namespace[] EMPTYNS = new Namespace[0]; + + final javax.xml.xpath.XPathExpression rawexpression; + final Namespace[] nsraw; + + /** + * Construct the XPathExpression. + * @param query The XPath query to create. + * @param filter The coercion filter. + * @param variables The variable map + * @param namespaces The scope namespaces. + * @param fac The XMLFactory to use for construction. + */ + public JavaXPathExpression(final String query, final Filter filter, + final Map variables, final Namespace[] namespaces, + final XPathFactory fac) { + super(query, filter, variables, namespaces); + nsraw = namespaces == null ? EMPTYNS : + ArrayCopy.copyOf(namespaces, namespaces.length); + final XPath xp = fac.newXPath(); + xp.setNamespaceContext(this); + xp.setXPathVariableResolver(this); + try { + rawexpression = xp.compile(query); + } catch (XPathExpressionException e) { + throw new IllegalArgumentException( + "Unable to compile expression: " + query, e); + } + } + + @Override + public String getNamespaceURI(String prefix) { + return getNamespace(prefix).getURI(); + } + + @Override + public String getPrefix(String namespaceURI) { + if (namespaceURI == null) { + // the API doc for NamespaceContext says to throw IllegalArgument + // not NullPointer. + throw new IllegalArgumentException("Null namespaceURI"); + } + if (JDOMConstants.NS_URI_XML.equals(namespaceURI)) { + return JDOMConstants.NS_PREFIX_XML; + } + if (JDOMConstants.NS_URI_XMLNS.equals(namespaceURI)) { + return JDOMConstants.NS_PREFIX_XMLNS; + } + for (Namespace ns : nsraw) { + if (namespaceURI.equals(ns.getURI())) { + return ns.getPrefix(); + } + } + return null; + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + if (namespaceURI == null) { + // the API doc for NamespaceContext says to throw IllegalArgument + // not NullPointer. + throw new IllegalArgumentException("Null namespaceURI"); + } + if (JDOMConstants.NS_URI_XML.equals(namespaceURI)) { + return Collections.singleton(JDOMConstants.NS_PREFIX_XML).iterator(); + } + if (JDOMConstants.NS_URI_XMLNS.equals(namespaceURI)) { + return Collections.singleton(JDOMConstants.NS_PREFIX_XMLNS).iterator(); + } + ArrayList pfx = new ArrayList(); + for (Namespace ns : nsraw) { + if (namespaceURI.equals(ns.getURI())) { + pfx.add(ns.getPrefix()); + } + } + return Collections.unmodifiableList(pfx).iterator(); + } + + @Override + public Object resolveVariable(QName variableName) { + return getVariable(variableName.getLocalPart(), + Namespace.getNamespace(variableName.getNamespaceURI())); + } + + private Object wrapContext(Object context) { + if (context instanceof Content) { + switch (((Content)context).getCType()) { + case CDATA : + return DOM.wrap((CDATA)context); + case Comment: + return DOM.wrap((Comment)context); + case DocType: + return DOM.wrap((DocType)context); + case Element: + return DOM.wrap((Element)context); + case EntityRef: + return DOM.wrap((EntityRef)context); + case ProcessingInstruction: + return DOM.wrap((ProcessingInstruction)context); + case Text: + return DOM.wrap((Text)context); + } + throw new IllegalStateException("Should never break out of swich"); + } else if (context instanceof Attribute) { + return DOM.wrap((Attribute)context); + } else if (context instanceof Document) { + return DOM.wrap((Document)context); + } else { + throw new IllegalArgumentException( + "Unable to process context: " + context); + } + } + + private Object unWrap(final Object o) { + if (o instanceof Wrapper) { + return ((Wrapper)o).getWrapped(); + } + if (o instanceof Attr) { + // odd one.... + // DOM has no node for Namespaces, so the typical XPath engine on + // DOM nodes returns an Attr representation for the Namespace. + // easy to check.... + Attr a = (Attr)o; + if (JDOMConstants.NS_URI_XMLNS.equals(a.getNamespaceURI())) { + // it is an xml declaration. + return Namespace.getNamespace(a.getLocalName(), a.getValue()); + } + if (JDOMConstants.NS_PREFIX_DEFAULT.equals(a.getNamespaceURI()) && + JDOMConstants.NS_PREFIX_XMLNS.equals(a.getLocalName())) { + return Namespace.getNamespace(a.getValue()); + } + } + return o; + } + + @Override + protected List evaluateRawAll(Object context) { + final Object ctx = wrapContext(context); + try { + final NodeList nl = (NodeList)rawexpression.evaluate( + ctx, XPathConstants.NODESET); + final int sz = nl.getLength(); + ArrayList ret = new ArrayList(sz); + for (int i = 0; i < sz; i++) { + ret.add(unWrap(nl.item(i))); + } + return ret; + } catch (XPathExpressionException e) { + throw new IllegalStateException( + "Unable to evaluate expression: " + this.toString(), e); + } + } + + @Override + protected Object evaluateRawFirst(Object context) { + final Object ctx = wrapContext(context); + try { + final NodeList nl = (NodeList)rawexpression.evaluate( + ctx, XPathConstants.NODESET); + if (nl.getLength() == 0) { + return null; + } + return unWrap(nl.item(0)); + } catch (XPathExpressionException e) { + throw new IllegalStateException( + "Unable to evaluate expression: " + this.toString(), e); + } + } + +} diff --git a/contrib/src/java/org/jdom/contrib/xpath/java/JavaXPathFactory.java b/contrib/src/java/org/jdom/contrib/xpath/java/JavaXPathFactory.java new file mode 100644 index 0000000..c265854 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/xpath/java/JavaXPathFactory.java @@ -0,0 +1,89 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.xpath.java; + +import java.util.Map; + +import javax.xml.xpath.XPathFactory; + +import org.jdom.Namespace; +import org.jdom.filter2.Filter; +import org.jdom.xpath.XPathExpression; + +/** + * An XPathFactory using the underlying infrastructure in javax.xml.xpath.* + * to process the XPath expressions against the JDOM content. + * @author Rolf Lear + * + */ +public class JavaXPathFactory extends org.jdom.xpath.XPathFactory { + + ThreadLocal localfac = + new ThreadLocal(); + + @Override + public XPathExpression compile(String expression, Filter filter, + Map variables, Namespace... namespaces) { + // Java XPath factories are not thread safe... use a thread-local. + XPathFactory fac = localfac.get(); + if (fac == null) { + fac = XPathFactory.newInstance(); + localfac.set(fac); + } + return new JavaXPathExpression( + expression, filter, variables, namespaces, fac); + } + +} diff --git a/contrib/src/java/org/jdom/contrib/xpath/xalan/JDOM2DTM.java b/contrib/src/java/org/jdom/contrib/xpath/xalan/JDOM2DTM.java new file mode 100644 index 0000000..ae38fd0 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/xpath/xalan/JDOM2DTM.java @@ -0,0 +1,475 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.xpath.xalan; + +import java.util.Collections; +import java.util.List; + +import javax.xml.transform.SourceLocator; + +import org.apache.xml.dtm.DTM; +import org.apache.xml.dtm.DTMManager; +import org.apache.xml.dtm.DTMWSFilter; +import org.apache.xml.dtm.ref.DTMDefaultBaseIterators; +import org.apache.xml.dtm.ref.DTMManagerDefault; +import org.apache.xml.dtm.ref.ExpandedNameTable; +import org.apache.xml.res.XMLErrorResources; +import org.apache.xml.res.XMLMessages; +import org.apache.xml.utils.XMLString; +import org.apache.xml.utils.XMLStringFactory; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; + +import org.jdom.Attribute; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.NamespaceAware; +import org.jdom.Parent; +import org.jdom.internal.ArrayCopy; +import org.jdom.transform.JDOMSource; +import org.jdom.util.NamespaceStack; + +/** + * *********************************************** + * THIS CODE IS NOT COMPLETE... DO NOT USE IT + * Marked Deprecated + * *********************************************** + * + * + * + * + * Create a DTM navigator to a JDOM document + * + * @author Rolf Lear + * + */ +@Deprecated +public class JDOM2DTM extends DTMDefaultBaseIterators { + + private static final int getType(NamespaceAware nsa) { + if (nsa instanceof Content) { + switch (((Content)nsa).getCType()) { + case CDATA: + return DTM.CDATA_SECTION_NODE; + case Comment: + return DTM.COMMENT_NODE; + case DocType: + return DTM.DOCUMENT_TYPE_NODE; + case Element: + return DTM.ELEMENT_NODE; + case EntityRef: + return DTM.ENTITY_REFERENCE_NODE; + case ProcessingInstruction: + return DTM.PROCESSING_INSTRUCTION_NODE; + case Text: + return DTM.TEXT_NODE; + } + } else if (nsa instanceof Document) { + return DTM.DOCUMENT_NODE; + } else if (nsa instanceof Attribute) { + return DTM.ATTRIBUTE_NODE; + } else if (nsa instanceof NamespacePointer) { + return DTM.NAMESPACE_NODE; + } + throw new IllegalStateException("Unknonw node type " + nsa); + } + + private NamespaceAware[] i_nodes = new NamespaceAware[1024]; + + private final String systemId, publicID; + + /** + * @param mgr The DTMManager + * @param source The Source + * @param dtmIdentity The dtmIdentity + * @param whiteSpaceFilter the whitespace filter + * @param xstringfactory the xstringfactory + * @param doIndexing the indexing flag. + */ + public JDOM2DTM(DTMManager mgr, JDOMSource source, int dtmIdentity, + DTMWSFilter whiteSpaceFilter, XMLStringFactory xstringfactory, + boolean doIndexing) { + super(mgr, source, dtmIdentity, whiteSpaceFilter, xstringfactory, doIndexing); + String pid = null, sid = null; + Parent root = source.getDocument(); + final NamespaceStack nstack = new NamespaceStack(); + // we do not play around with 'on-demand' building of the tree. + // we build the whole thing 'one-shot'. maybe at some point we can + // make the process on-demand. + if (root != null) { + // the root is a document node. + final Document doc = (Document)root; + setDocumentBaseURI(doc.getBaseURI()); + DocType dt = doc.getDocType(); + if (dt != null) { + sid = dt.getSystemID(); + pid = dt.getPublicID(); + } + if (sid == null) { + sid = doc.getBaseURI(); + } + + addNodes(nstack, NULL, Collections.singletonList(doc)); + } else { + addNodes(nstack, NULL, source.getNodes()); + } + systemId = sid; + publicID = pid; + } + + private int addNode(final NamespaceStack nstack, final int parent, + final int prevsib, final NamespaceAware nsa) { + + final int nodeIndex = m_size++; + if (nodeIndex >= i_nodes.length) { + // add 50% + i_nodes = ArrayCopy.copyOf(i_nodes, nodeIndex + (nodeIndex >> 1) + 1); + } + i_nodes[nodeIndex] = nsa; + + // copied from DOM2DTM.java .. not sure what this does.... + // Have we overflowed a DTM Identity's addressing range? + if(m_dtmIdent.size() == (nodeIndex>>>DTMManager.IDENT_DTM_NODE_BITS)) { + if(m_mgr==null) { + //"No more DTM IDs are available"; + error(XMLMessages.createXMLMessage(XMLErrorResources.ER_NO_DTMIDS_AVAIL, null)); + } + + // Handle as Extended Addressing + final DTMManagerDefault mgrD=(DTMManagerDefault)m_mgr; + final int id=mgrD.getFirstFreeDTMID(); + mgrD.addDTM(this,id,nodeIndex); + m_dtmIdent.addElement(id< 0) { + addNodes(nstack, nodeIndex, element.getContent()); + } + } finally { + nstack.pop(); + } + } else { + // must be Document. + if (pn.getContentSize() > 0) { + addNodes(nstack, nodeIndex, pn.getContent()); + } + } + } + + return nodeIndex; + } + + private void addNodes(NamespaceStack nstack, int parentIndex, + List list) { + final int cs = list.size(); + if (cs > 0) { + int pvid = NULL; + int cid = NULL; + for (int i = 0; i < cs; i++) { + final NamespaceAware c = list.get(i); + cid = addNode(nstack, parentIndex, pvid, c); + if (pvid == NULL) { + m_firstch.setElementAt(cid, parentIndex); + } else { + m_nextsib.setElementAt(cid, pvid); + } + pvid = cid; + } + if (cid != NULL) { + m_nextsib.setElementAt(NULL, cid); + } + } + + } + + @Override + protected int getNextNodeIdentity(int identity) { + identity += 1; + + if (identity >= m_size) { + identity = DTM.NULL; + } + + return identity; + } + + @Override + protected boolean nextNode() { + // we pre-build the entire node, so this is always false. + return false; + } + + @Override + protected int getNumberOfNodes() { + // we prebuild, so always false. + return m_size; + } + + @Override + public int getAttributeNode(int nodeHandle, String namespaceURI, String name) { + + return 0; + } + + @Override + public XMLString getStringValue(int nodeHandle) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getNodeName(int nodeHandle) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getLocalName(int nodeHandle) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getPrefix(int nodeHandle) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getNamespaceURI(int nodeHandle) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getNodeValue(int nodeHandle) { + // TODO Auto-generated method stub + return null; + } + + @Override + public String getDocumentTypeDeclarationSystemIdentifier() { + return systemId; + } + + @Override + public String getDocumentTypeDeclarationPublicIdentifier() { + return publicID; + } + + @Override + public int getElementById(String elementId) { + // TODO Auto-generated method stub + return 0; + } + + @Override + public String getUnparsedEntityURI(String name) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean isAttributeSpecified(int attributeHandle) { + // all attributes are specified + return true; + } + + @Override + public SourceLocator getSourceLocatorFor(int node) { + return null; + } + + @Override + public void setProperty(String property, Object value) { + // nothing. + } + + @Override + public boolean needsTwoThreads() { + return false; + } + + + /* *************************************************** + * Unneeded SAX-Based methods. + * *************************************************** */ + + @Override + public ContentHandler getContentHandler() { + // nothing + return null; + } + + @Override + public LexicalHandler getLexicalHandler() { + // nothing + return null; + } + + @Override + public EntityResolver getEntityResolver() { + // nothing + return null; + } + + @Override + public DTDHandler getDTDHandler() { + // nothing + return null; + } + + @Override + public ErrorHandler getErrorHandler() { + // nothing + return null; + } + + @Override + public DeclHandler getDeclHandler() { + // nothing + return null; + } + + @Override + public void dispatchCharactersEvents(int nodeHandle, ContentHandler ch, + boolean normalize) throws SAXException { + // nothing + } + + @Override + public void dispatchToEvents(int nodeHandle, ContentHandler ch) + throws SAXException { + // nothing + } + +} diff --git a/contrib/src/java/org/jdom/contrib/xpath/xalan/NamespacePointer.java b/contrib/src/java/org/jdom/contrib/xpath/xalan/NamespacePointer.java new file mode 100644 index 0000000..9ffed71 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/xpath/xalan/NamespacePointer.java @@ -0,0 +1,114 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.xpath.xalan; + +import java.util.List; + +import org.jdom.Namespace; +import org.jdom.NamespaceAware; + +/** + * *********************************************** + * THIS CODE IS NOT COMPLETE... DO NOT USE IT + * Marked Deprecated + * *********************************************** + * + * + * + * + * Simple class to wrap a Namespace declaration. + * + * @author Rolf Lear + * + */ +@Deprecated +class NamespacePointer implements NamespaceAware { + + private final Namespace namespace; + + /** + * @param namespace The namespace to point to. + */ + public NamespacePointer(Namespace namespace) { + super(); + this.namespace = namespace; + } + + /** + * Get the underlyng namespace + * @return the underlying namespace + */ + public Namespace getNamespace() { + return namespace; + } + + + + @Override + public List getNamespacesInScope() { + return null; + } + + @Override + public List getNamespacesIntroduced() { + return null; + } + + @Override + public List getNamespacesInherited() { + return null; + } + +} diff --git a/contrib/src/java/org/jdom/contrib/xpath/xalan/XalanXPathExpression.java b/contrib/src/java/org/jdom/contrib/xpath/xalan/XalanXPathExpression.java new file mode 100644 index 0000000..64ee2fa --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/xpath/xalan/XalanXPathExpression.java @@ -0,0 +1,272 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.xpath.xalan; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.xml.transform.TransformerException; + +import org.apache.xml.utils.PrefixResolver; +import org.apache.xml.utils.QName; +import org.apache.xpath.VariableStack; +import org.apache.xpath.XPath; +import org.apache.xpath.XPathContext; +import org.apache.xpath.axes.NodeSequence; +import org.apache.xpath.objects.XObject; +import org.w3c.dom.Attr; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.NamespaceAware; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.contrib.dom.DOM; +import org.jdom.contrib.dom.Wrapper; +import org.jdom.filter2.Filter; +import org.jdom.xpath.util.AbstractXPathCompiled; + +/** + * An XPathExpression that wraps the JDOM content in DOM wrappers + * and then uses the Direct Xalan API to implement XPath. + * + * @author Rolf Lear + * + * @param the type of the coerced results. + */ +class XalanXPathExpression extends AbstractXPathCompiled implements PrefixResolver { + + private final XPath xpath; + + private final VariableStack variables = new VariableStack() { + @Override + public XObject getVariableOrParam(XPathContext xctxt, QName qname) + throws TransformerException { + if (qname == null) { + throw new IllegalArgumentException("Null qname"); + } + final Object varValue = getVariable(qname.getLocalName(), + Namespace.getNamespace(qname.getNamespaceURI())); + if ( varValue == null ) { + throw new TransformerException( + "No such variable " + qname.toNamespacedString()); + } + return XObject.create(varValue, xctxt); + } + }; + + /** + * Construct the XPathExpression. + * @param query The XPath query to create. + * @param filter The coercion filter. + * @param variables The variable map + * @param namespaces The scope namespaces. + */ + public XalanXPathExpression(final String query, final Filter filter, + final Map variables, final Namespace[] namespaces) { + super(query, filter, variables, namespaces); + + // Create an object to resolve namespace prefixes. + // XPath namespaces are resolved from the input context node's document element + // if it is a root node, or else the current context node (for lack of a better + // resolution space, given the simplicity of this sample code). + + // Create the XPath object. + try { + xpath = new XPath(query, null, this, XPath.SELECT, null); + } catch (TransformerException e) { + throw new IllegalArgumentException("Unable to parse: " + query, e); + } + + } + + private final Node wrap(Object context) { + if (context == null) { + return DOM.wrap(new Document(), false); + } + if (context instanceof Document) { + return DOM.wrap((Document)context); + } + if (context instanceof Content) { + switch (((Content)context).getCType()) { + case CDATA: + return DOM.wrap((CDATA)context); + case Comment: + return DOM.wrap((Comment)context); + case DocType: + return DOM.wrap((DocType)context); + case Element: + return DOM.wrap((Element)context); + case EntityRef: + return DOM.wrap((EntityRef)context); + case ProcessingInstruction: + return DOM.wrap((ProcessingInstruction)context); + case Text: + return DOM.wrap((Text)context); + } + } + if (context instanceof Attribute) { + return DOM.wrap((Attribute)context); + } + throw new IllegalArgumentException("Unable to wrap context: " + context); + } + + @Override + protected List evaluateRawAll(Object context) { + + // Execute the XPath, and have it return the result + // return xpath.execute(xpathSupport, contextNode, prefixResolver); + final XPathContext xpathSupport = new XPathContext(false); + xpathSupport.setVarStack(variables); + + final Node contextNode = wrap(context); + final int ctxtNode = xpathSupport.getDTMHandleFromNode(contextNode); + + try { + final XObject xo = xpath.execute(xpathSupport, ctxtNode, this); + ArrayList ret = new ArrayList(); + if (xo instanceof NodeSequence) { + final NodeList nl = ((NodeSequence)xo).nodelist(); + final int len = nl.getLength(); + for (int i = 0; i < len; i++) { + Node n = nl.item(i); + if (n instanceof Wrapper) { + ret.add(((Wrapper)n).getWrapped()); + } else if (n instanceof Attr) { + // probably a Namespace in the form of an Attr. + final Attr nsa = (Attr)n; + if ("xmlns".equals(nsa.getLocalName())) { + ret.add(Namespace.getNamespace(nsa.getValue())); + } else if (nsa.getName().startsWith("xmlns")) { + ret.add(Namespace.getNamespace( + nsa.getLocalName(), nsa.getValue())); + } else { + throw new IllegalStateException( + "Unexpected Attribute " + nsa.getName() + + "=\"" + nsa.getValue() + "\""); + } + } else { + throw new IllegalStateException( + "Unexpected Node " + n.getNodeName()); + } + } + } else { + ret.add(xo.object()); + } + + return ret; + } catch (TransformerException te) { + throw new IllegalArgumentException("Unable to process xpath.", te); + } + } + + @Override + protected Object evaluateRawFirst(Object context) { + List raw = evaluateRawAll(context); + if (raw.isEmpty()) { + return null; + } + return raw.get(0); + } + + + @Override + public String getNamespaceForPrefix(String prefix) { + return getNamespace(prefix).getURI(); + } + + @Override + public String getNamespaceForPrefix(String prefix, Node context) { + if (context == null) { + return getNamespace(prefix).getPrefix(); + } + if (prefix == null) { + prefix = ""; + } + if (context instanceof Wrapper) { + Object o = ((Wrapper)context).getWrapped(); + if ((o instanceof NamespaceAware)) { + for (Namespace ns : ((NamespaceAware)o).getNamespacesInScope()) { + if (ns.getPrefix().equals(prefix)) { + return ns.getURI(); + } + } + } + } + return null; + } + + @Override + public String getBaseIdentifier() { + return null; + } + + @Override + public boolean handlesNullPrefixes() { + return false; + } + + +} diff --git a/contrib/src/java/org/jdom/contrib/xpath/xalan/XalanXPathFactory.java b/contrib/src/java/org/jdom/contrib/xpath/xalan/XalanXPathFactory.java new file mode 100644 index 0000000..ecd9352 --- /dev/null +++ b/contrib/src/java/org/jdom/contrib/xpath/xalan/XalanXPathFactory.java @@ -0,0 +1,79 @@ +/*-- + + Copyright (C) 2011-2014 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.contrib.xpath.xalan; + +import java.util.Map; + +import org.jdom.Namespace; +import org.jdom.filter2.Filter; +import org.jdom.xpath.XPathExpression; + +/** + * An XPathFactory that wraps the JDOM content in a thin DOM layer, and then + * uses that to seed the Xalan API for XPath processing. + * @author Rolf Lear + * + */ +public class XalanXPathFactory extends org.jdom.xpath.XPathFactory { + + @Override + public XPathExpression compile(String expression, Filter filter, + Map variables, Namespace... namespaces) { + // Java XPath factories are not thread safe... use a thread-local. + return new XalanXPathExpression( + expression, filter, variables, namespaces); + } + +} diff --git a/contrib/src/resources/DTDAttributeDefault.dtd b/contrib/src/resources/DTDAttributeDefault.dtd new file mode 100644 index 0000000..7b2d8ae --- /dev/null +++ b/contrib/src/resources/DTDAttributeDefault.dtd @@ -0,0 +1,6 @@ + + + + diff --git a/contrib/src/resources/DTDAttributeDefault.xml b/contrib/src/resources/DTDAttributeDefault.xml new file mode 100644 index 0000000..b7d3806 --- /dev/null +++ b/contrib/src/resources/DTDAttributeDefault.xml @@ -0,0 +1,4 @@ + + + + diff --git a/contrib/src/resources/hamlet.xml b/contrib/src/resources/hamlet.xml new file mode 100644 index 0000000..00bffda --- /dev/null +++ b/contrib/src/resources/hamlet.xml @@ -0,0 +1,9151 @@ + + + + +The Tragedy of Hamlet, Prince of Denmark + + +

Text placed in the public domain by Moby Lexical Tools, 1992.

+

SGML markup by Jon Bosak, 1992-1994.

+

XML version by Jon Bosak, 1996-1997.

+

This work may be freely copied and distributed worldwide.

+
+ + + +Dramatis Personae + +CLAUDIUS, king of Denmark. +HAMLET, son to the late, and nephew to the present king. +POLONIUS, lord chamberlain. +HORATIO, friend to Hamlet. +LAERTES, son to Polonius. +LUCIANUS, nephew to the king. + + +VOLTIMAND +CORNELIUS +ROSENCRANTZ +GUILDENSTERN +OSRIC +courtiers. + + +A Gentleman +A Priest. + + +MARCELLUS +BERNARDO +officers. + + +FRANCISCO, a soldier. +REYNALDO, servant to Polonius. +Players. +Two Clowns, grave-diggers. +FORTINBRAS, prince of Norway. +A Captain. +English Ambassadors. +GERTRUDE, queen of Denmark, and mother to Hamlet. +OPHELIA, daughter to Polonius. +Lords, Ladies, Officers, Soldiers, Sailors, Messengers, and other Attendants. +Ghost of Hamlet's Father. + + +SCENE Denmark. + +HAMLET + +ACT I + +SCENE I. Elsinore. A platform before the castle. +FRANCISCO at his post. Enter to him BERNARDO + + +BERNARDO +Who's there? + + + +FRANCISCO +Nay, answer me: stand, and unfold yourself. + + + +BERNARDO +Long live the king! + + + +FRANCISCO +Bernardo? + + + +BERNARDO +He. + + + +FRANCISCO +You come most carefully upon your hour. + + + +BERNARDO +'Tis now struck twelve; get thee to bed, Francisco. + + + +FRANCISCO +For this relief much thanks: 'tis bitter cold, +And I am sick at heart. + + + +BERNARDO +Have you had quiet guard? + + + +FRANCISCO +Not a mouse stirring. + + + +BERNARDO +Well, good night. +If you do meet Horatio and Marcellus, +The rivals of my watch, bid them make haste. + + + +FRANCISCO +I think I hear them. Stand, ho! Who's there? + + + +Enter HORATIO and MARCELLUS + + +HORATIO +Friends to this ground. + + + +MARCELLUS +And liegemen to the Dane. + + + +FRANCISCO +Give you good night. + + + +MARCELLUS +O, farewell, honest soldier: +Who hath relieved you? + + + +FRANCISCO +Bernardo has my place. +Give you good night. + + + +Exit + + +MARCELLUS +Holla! Bernardo! + + + +BERNARDO +Say, +What, is Horatio there? + + + +HORATIO +A piece of him. + + + +BERNARDO +Welcome, Horatio: welcome, good Marcellus. + + + +MARCELLUS +What, has this thing appear'd again to-night? + + + +BERNARDO +I have seen nothing. + + + +MARCELLUS +Horatio says 'tis but our fantasy, +And will not let belief take hold of him +Touching this dreaded sight, twice seen of us: +Therefore I have entreated him along +With us to watch the minutes of this night; +That if again this apparition come, +He may approve our eyes and speak to it. + + + +HORATIO +Tush, tush, 'twill not appear. + + + +BERNARDO +Sit down awhile; +And let us once again assail your ears, +That are so fortified against our story +What we have two nights seen. + + + +HORATIO +Well, sit we down, +And let us hear Bernardo speak of this. + + + +BERNARDO +Last night of all, +When yond same star that's westward from the pole +Had made his course to illume that part of heaven +Where now it burns, Marcellus and myself, +The bell then beating one,-- + + + +Enter Ghost + + +MARCELLUS +Peace, break thee off; look, where it comes again! + + + +BERNARDO +In the same figure, like the king that's dead. + + + +MARCELLUS +Thou art a scholar; speak to it, Horatio. + + + +BERNARDO +Looks it not like the king? mark it, Horatio. + + + +HORATIO +Most like: it harrows me with fear and wonder. + + + +BERNARDO +It would be spoke to. + + + +MARCELLUS +Question it, Horatio. + + + +HORATIO +What art thou that usurp'st this time of night, +Together with that fair and warlike form +In which the majesty of buried Denmark +Did sometimes march? by heaven I charge thee, speak! + + + +MARCELLUS +It is offended. + + + +BERNARDO +See, it stalks away! + + + +HORATIO +Stay! speak, speak! I charge thee, speak! + + + +Exit Ghost + + +MARCELLUS +'Tis gone, and will not answer. + + + +BERNARDO +How now, Horatio! you tremble and look pale: +Is not this something more than fantasy? +What think you on't? + + + +HORATIO +Before my God, I might not this believe +Without the sensible and true avouch +Of mine own eyes. + + + +MARCELLUS +Is it not like the king? + + + +HORATIO +As thou art to thyself: +Such was the very armour he had on +When he the ambitious Norway combated; +So frown'd he once, when, in an angry parle, +He smote the sledded Polacks on the ice. +'Tis strange. + + + +MARCELLUS +Thus twice before, and jump at this dead hour, +With martial stalk hath he gone by our watch. + + + +HORATIO +In what particular thought to work I know not; +But in the gross and scope of my opinion, +This bodes some strange eruption to our state. + + + +MARCELLUS +Good now, sit down, and tell me, he that knows, +Why this same strict and most observant watch +So nightly toils the subject of the land, +And why such daily cast of brazen cannon, +And foreign mart for implements of war; +Why such impress of shipwrights, whose sore task +Does not divide the Sunday from the week; +What might be toward, that this sweaty haste +Doth make the night joint-labourer with the day: +Who is't that can inform me? + + + +HORATIO +That can I; +At least, the whisper goes so. Our last king, +Whose image even but now appear'd to us, +Was, as you know, by Fortinbras of Norway, +Thereto prick'd on by a most emulate pride, +Dared to the combat; in which our valiant Hamlet-- +For so this side of our known world esteem'd him-- +Did slay this Fortinbras; who by a seal'd compact, +Well ratified by law and heraldry, +Did forfeit, with his life, all those his lands +Which he stood seized of, to the conqueror: +Against the which, a moiety competent +Was gaged by our king; which had return'd +To the inheritance of Fortinbras, +Had he been vanquisher; as, by the same covenant, +And carriage of the article design'd, +His fell to Hamlet. Now, sir, young Fortinbras, +Of unimproved mettle hot and full, +Hath in the skirts of Norway here and there +Shark'd up a list of lawless resolutes, +For food and diet, to some enterprise +That hath a stomach in't; which is no other-- +As it doth well appear unto our state-- +But to recover of us, by strong hand +And terms compulsatory, those foresaid lands +So by his father lost: and this, I take it, +Is the main motive of our preparations, +The source of this our watch and the chief head +Of this post-haste and romage in the land. + + + +BERNARDO +I think it be no other but e'en so: +Well may it sort that this portentous figure +Comes armed through our watch; so like the king +That was and is the question of these wars. + + + +HORATIO +A mote it is to trouble the mind's eye. +In the most high and palmy state of Rome, +A little ere the mightiest Julius fell, +The graves stood tenantless and the sheeted dead +Did squeak and gibber in the Roman streets: +As stars with trains of fire and dews of blood, +Disasters in the sun; and the moist star +Upon whose influence Neptune's empire stands +Was sick almost to doomsday with eclipse: +And even the like precurse of fierce events, +As harbingers preceding still the fates +And prologue to the omen coming on, +Have heaven and earth together demonstrated +Unto our climatures and countrymen.-- +But soft, behold! lo, where it comes again! +Re-enter Ghost +I'll cross it, though it blast me. Stay, illusion! +If thou hast any sound, or use of voice, +Speak to me: +If there be any good thing to be done, +That may to thee do ease and grace to me, +Speak to me: +Cock crows +If thou art privy to thy country's fate, +Which, happily, foreknowing may avoid, O, speak! +Or if thou hast uphoarded in thy life +Extorted treasure in the womb of earth, +For which, they say, you spirits oft walk in death, +Speak of it: stay, and speak! Stop it, Marcellus. + + + +MARCELLUS +Shall I strike at it with my partisan? + + + +HORATIO +Do, if it will not stand. + + + +BERNARDO +'Tis here! + + + +HORATIO +'Tis here! + + + +MARCELLUS +'Tis gone! +Exit Ghost +We do it wrong, being so majestical, +To offer it the show of violence; +For it is, as the air, invulnerable, +And our vain blows malicious mockery. + + + +BERNARDO +It was about to speak, when the cock crew. + + + +HORATIO +And then it started like a guilty thing +Upon a fearful summons. I have heard, +The cock, that is the trumpet to the morn, +Doth with his lofty and shrill-sounding throat +Awake the god of day; and, at his warning, +Whether in sea or fire, in earth or air, +The extravagant and erring spirit hies +To his confine: and of the truth herein +This present object made probation. + + + +MARCELLUS +It faded on the crowing of the cock. +Some say that ever 'gainst that season comes +Wherein our Saviour's birth is celebrated, +The bird of dawning singeth all night long: +And then, they say, no spirit dares stir abroad; +The nights are wholesome; then no planets strike, +No fairy takes, nor witch hath power to charm, +So hallow'd and so gracious is the time. + + + +HORATIO +So have I heard and do in part believe it. +But, look, the morn, in russet mantle clad, +Walks o'er the dew of yon high eastward hill: +Break we our watch up; and by my advice, +Let us impart what we have seen to-night +Unto young Hamlet; for, upon my life, +This spirit, dumb to us, will speak to him. +Do you consent we shall acquaint him with it, +As needful in our loves, fitting our duty? + + + +MARCELLUS +Let's do't, I pray; and I this morning know +Where we shall find him most conveniently. + + + +Exeunt + + +SCENE II. A room of state in the castle. +Enter KING CLAUDIUS, QUEEN GERTRUDE, HAMLET, +POLONIUS, LAERTES, VOLTIMAND, CORNELIUS, Lords, +and Attendants + + +KING CLAUDIUS +Though yet of Hamlet our dear brother's death +The memory be green, and that it us befitted +To bear our hearts in grief and our whole kingdom +To be contracted in one brow of woe, +Yet so far hath discretion fought with nature +That we with wisest sorrow think on him, +Together with remembrance of ourselves. +Therefore our sometime sister, now our queen, +The imperial jointress to this warlike state, +Have we, as 'twere with a defeated joy,-- +With an auspicious and a dropping eye, +With mirth in funeral and with dirge in marriage, +In equal scale weighing delight and dole,-- +Taken to wife: nor have we herein barr'd +Your better wisdoms, which have freely gone +With this affair along. For all, our thanks. +Now follows, that you know, young Fortinbras, +Holding a weak supposal of our worth, +Or thinking by our late dear brother's death +Our state to be disjoint and out of frame, +Colleagued with the dream of his advantage, +He hath not fail'd to pester us with message, +Importing the surrender of those lands +Lost by his father, with all bonds of law, +To our most valiant brother. So much for him. +Now for ourself and for this time of meeting: +Thus much the business is: we have here writ +To Norway, uncle of young Fortinbras,-- +Who, impotent and bed-rid, scarcely hears +Of this his nephew's purpose,--to suppress +His further gait herein; in that the levies, +The lists and full proportions, are all made +Out of his subject: and we here dispatch +You, good Cornelius, and you, Voltimand, +For bearers of this greeting to old Norway; +Giving to you no further personal power +To business with the king, more than the scope +Of these delated articles allow. +Farewell, and let your haste commend your duty. + + + +CORNELIUS +VOLTIMAND +In that and all things will we show our duty. + + + +KING CLAUDIUS +We doubt it nothing: heartily farewell. +Exeunt VOLTIMAND and CORNELIUS +And now, Laertes, what's the news with you? +You told us of some suit; what is't, Laertes? +You cannot speak of reason to the Dane, +And loose your voice: what wouldst thou beg, Laertes, +That shall not be my offer, not thy asking? +The head is not more native to the heart, +The hand more instrumental to the mouth, +Than is the throne of Denmark to thy father. +What wouldst thou have, Laertes? + + + +LAERTES +My dread lord, +Your leave and favour to return to France; +From whence though willingly I came to Denmark, +To show my duty in your coronation, +Yet now, I must confess, that duty done, +My thoughts and wishes bend again toward France +And bow them to your gracious leave and pardon. + + + +KING CLAUDIUS +Have you your father's leave? What says Polonius? + + + +LORD POLONIUS +He hath, my lord, wrung from me my slow leave +By laboursome petition, and at last +Upon his will I seal'd my hard consent: +I do beseech you, give him leave to go. + + + +KING CLAUDIUS +Take thy fair hour, Laertes; time be thine, +And thy best graces spend it at thy will! +But now, my cousin Hamlet, and my son,-- + + + +HAMLET +Aside A little more than kin, and less than kind. + + + +KING CLAUDIUS +How is it that the clouds still hang on you? + + + +HAMLET +Not so, my lord; I am too much i' the sun. + + + +QUEEN GERTRUDE +Good Hamlet, cast thy nighted colour off, +And let thine eye look like a friend on Denmark. +Do not for ever with thy vailed lids +Seek for thy noble father in the dust: +Thou know'st 'tis common; all that lives must die, +Passing through nature to eternity. + + + +HAMLET +Ay, madam, it is common. + + + +QUEEN GERTRUDE +If it be, +Why seems it so particular with thee? + + + +HAMLET +Seems, madam! nay it is; I know not 'seems.' +'Tis not alone my inky cloak, good mother, +Nor customary suits of solemn black, +Nor windy suspiration of forced breath, +No, nor the fruitful river in the eye, +Nor the dejected 'havior of the visage, +Together with all forms, moods, shapes of grief, +That can denote me truly: these indeed seem, +For they are actions that a man might play: +But I have that within which passeth show; +These but the trappings and the suits of woe. + + + +KING CLAUDIUS +'Tis sweet and commendable in your nature, Hamlet, +To give these mourning duties to your father: +But, you must know, your father lost a father; +That father lost, lost his, and the survivor bound +In filial obligation for some term +To do obsequious sorrow: but to persever +In obstinate condolement is a course +Of impious stubbornness; 'tis unmanly grief; +It shows a will most incorrect to heaven, +A heart unfortified, a mind impatient, +An understanding simple and unschool'd: +For what we know must be and is as common +As any the most vulgar thing to sense, +Why should we in our peevish opposition +Take it to heart? Fie! 'tis a fault to heaven, +A fault against the dead, a fault to nature, +To reason most absurd: whose common theme +Is death of fathers, and who still hath cried, +From the first corse till he that died to-day, +'This must be so.' We pray you, throw to earth +This unprevailing woe, and think of us +As of a father: for let the world take note, +You are the most immediate to our throne; +And with no less nobility of love +Than that which dearest father bears his son, +Do I impart toward you. For your intent +In going back to school in Wittenberg, +It is most retrograde to our desire: +And we beseech you, bend you to remain +Here, in the cheer and comfort of our eye, +Our chiefest courtier, cousin, and our son. + + + +QUEEN GERTRUDE +Let not thy mother lose her prayers, Hamlet: +I pray thee, stay with us; go not to Wittenberg. + + + +HAMLET +I shall in all my best obey you, madam. + + + +KING CLAUDIUS +Why, 'tis a loving and a fair reply: +Be as ourself in Denmark. Madam, come; +This gentle and unforced accord of Hamlet +Sits smiling to my heart: in grace whereof, +No jocund health that Denmark drinks to-day, +But the great cannon to the clouds shall tell, +And the king's rouse the heavens all bruit again, +Re-speaking earthly thunder. Come away. + + + +Exeunt all but HAMLET + + +HAMLET +O, that this too too solid flesh would melt +Thaw and resolve itself into a dew! +Or that the Everlasting had not fix'd +His canon 'gainst self-slaughter! O God! God! +How weary, stale, flat and unprofitable, +Seem to me all the uses of this world! +Fie on't! ah fie! 'tis an unweeded garden, +That grows to seed; things rank and gross in nature +Possess it merely. That it should come to this! +But two months dead: nay, not so much, not two: +So excellent a king; that was, to this, +Hyperion to a satyr; so loving to my mother +That he might not beteem the winds of heaven +Visit her face too roughly. Heaven and earth! +Must I remember? why, she would hang on him, +As if increase of appetite had grown +By what it fed on: and yet, within a month-- +Let me not think on't--Frailty, thy name is woman!-- +A little month, or ere those shoes were old +With which she follow'd my poor father's body, +Like Niobe, all tears:--why she, even she-- +O, God! a beast, that wants discourse of reason, +Would have mourn'd longer--married with my uncle, +My father's brother, but no more like my father +Than I to Hercules: within a month: +Ere yet the salt of most unrighteous tears +Had left the flushing in her galled eyes, +She married. O, most wicked speed, to post +With such dexterity to incestuous sheets! +It is not nor it cannot come to good: +But break, my heart; for I must hold my tongue. + + + +Enter HORATIO, MARCELLUS, and BERNARDO + + +HORATIO +Hail to your lordship! + + + +HAMLET +I am glad to see you well: +Horatio,--or I do forget myself. + + + +HORATIO +The same, my lord, and your poor servant ever. + + + +HAMLET +Sir, my good friend; I'll change that name with you: +And what make you from Wittenberg, Horatio? Marcellus? + + + +MARCELLUS +My good lord-- + + + +HAMLET +I am very glad to see you. Good even, sir. +But what, in faith, make you from Wittenberg? + + + +HORATIO +A truant disposition, good my lord. + + + +HAMLET +I would not hear your enemy say so, +Nor shall you do mine ear that violence, +To make it truster of your own report +Against yourself: I know you are no truant. +But what is your affair in Elsinore? +We'll teach you to drink deep ere you depart. + + + +HORATIO +My lord, I came to see your father's funeral. + + + +HAMLET +I pray thee, do not mock me, fellow-student; +I think it was to see my mother's wedding. + + + +HORATIO +Indeed, my lord, it follow'd hard upon. + + + +HAMLET +Thrift, thrift, Horatio! the funeral baked meats +Did coldly furnish forth the marriage tables. +Would I had met my dearest foe in heaven +Or ever I had seen that day, Horatio! +My father!--methinks I see my father. + + + +HORATIO +Where, my lord? + + + +HAMLET +In my mind's eye, Horatio. + + + +HORATIO +I saw him once; he was a goodly king. + + + +HAMLET +He was a man, take him for all in all, +I shall not look upon his like again. + + + +HORATIO +My lord, I think I saw him yesternight. + + + +HAMLET +Saw? who? + + + +HORATIO +My lord, the king your father. + + + +HAMLET +The king my father! + + + +HORATIO +Season your admiration for awhile +With an attent ear, till I may deliver, +Upon the witness of these gentlemen, +This marvel to you. + + + +HAMLET +For God's love, let me hear. + + + +HORATIO +Two nights together had these gentlemen, +Marcellus and Bernardo, on their watch, +In the dead vast and middle of the night, +Been thus encounter'd. A figure like your father, +Armed at point exactly, cap-a-pe, +Appears before them, and with solemn march +Goes slow and stately by them: thrice he walk'd +By their oppress'd and fear-surprised eyes, +Within his truncheon's length; whilst they, distilled +Almost to jelly with the act of fear, +Stand dumb and speak not to him. This to me +In dreadful secrecy impart they did; +And I with them the third night kept the watch; +Where, as they had deliver'd, both in time, +Form of the thing, each word made true and good, +The apparition comes: I knew your father; +These hands are not more like. + + + +HAMLET +But where was this? + + + +MARCELLUS +My lord, upon the platform where we watch'd. + + + +HAMLET +Did you not speak to it? + + + +HORATIO +My lord, I did; +But answer made it none: yet once methought +It lifted up its head and did address +Itself to motion, like as it would speak; +But even then the morning cock crew loud, +And at the sound it shrunk in haste away, +And vanish'd from our sight. + + + +HAMLET +'Tis very strange. + + + +HORATIO +As I do live, my honour'd lord, 'tis true; +And we did think it writ down in our duty +To let you know of it. + + + +HAMLET +Indeed, indeed, sirs, but this troubles me. +Hold you the watch to-night? + + + +MARCELLUS +BERNARDO +We do, my lord. + + + +HAMLET +Arm'd, say you? + + + +MARCELLUS +BERNARDO +Arm'd, my lord. + + + +HAMLET +From top to toe? + + + +MARCELLUS +BERNARDO +My lord, from head to foot. + + + +HAMLET +Then saw you not his face? + + + +HORATIO +O, yes, my lord; he wore his beaver up. + + + +HAMLET +What, look'd he frowningly? + + + +HORATIO +A countenance more in sorrow than in anger. + + + +HAMLET +Pale or red? + + + +HORATIO +Nay, very pale. + + + +HAMLET +And fix'd his eyes upon you? + + + +HORATIO +Most constantly. + + + +HAMLET +I would I had been there. + + + +HORATIO +It would have much amazed you. + + + +HAMLET +Very like, very like. Stay'd it long? + + + +HORATIO +While one with moderate haste might tell a hundred. + + + +MARCELLUS +BERNARDO +Longer, longer. + + + +HORATIO +Not when I saw't. + + + +HAMLET +His beard was grizzled--no? + + + +HORATIO +It was, as I have seen it in his life, +A sable silver'd. + + + +HAMLET +I will watch to-night; +Perchance 'twill walk again. + + + +HORATIO +I warrant it will. + + + +HAMLET +If it assume my noble father's person, +I'll speak to it, though hell itself should gape +And bid me hold my peace. I pray you all, +If you have hitherto conceal'd this sight, +Let it be tenable in your silence still; +And whatsoever else shall hap to-night, +Give it an understanding, but no tongue: +I will requite your loves. So, fare you well: +Upon the platform, 'twixt eleven and twelve, +I'll visit you. + + + +All +Our duty to your honour. + + + +HAMLET +Your loves, as mine to you: farewell. +Exeunt all but HAMLET +My father's spirit in arms! all is not well; +I doubt some foul play: would the night were come! +Till then sit still, my soul: foul deeds will rise, +Though all the earth o'erwhelm them, to men's eyes. + + + +Exit + + +SCENE III. A room in Polonius' house. +Enter LAERTES and OPHELIA + + +LAERTES +My necessaries are embark'd: farewell: +And, sister, as the winds give benefit +And convoy is assistant, do not sleep, +But let me hear from you. + + + +OPHELIA +Do you doubt that? + + + +LAERTES +For Hamlet and the trifling of his favour, +Hold it a fashion and a toy in blood, +A violet in the youth of primy nature, +Forward, not permanent, sweet, not lasting, +The perfume and suppliance of a minute; No more. + + + +OPHELIA +No more but so? + + + +LAERTES +Think it no more; +For nature, crescent, does not grow alone +In thews and bulk, but, as this temple waxes, +The inward service of the mind and soul +Grows wide withal. Perhaps he loves you now, +And now no soil nor cautel doth besmirch +The virtue of his will: but you must fear, +His greatness weigh'd, his will is not his own; +For he himself is subject to his birth: +He may not, as unvalued persons do, +Carve for himself; for on his choice depends +The safety and health of this whole state; +And therefore must his choice be circumscribed +Unto the voice and yielding of that body +Whereof he is the head. Then if he says he loves you, +It fits your wisdom so far to believe it +As he in his particular act and place +May give his saying deed; which is no further +Than the main voice of Denmark goes withal. +Then weigh what loss your honour may sustain, +If with too credent ear you list his songs, +Or lose your heart, or your chaste treasure open +To his unmaster'd importunity. +Fear it, Ophelia, fear it, my dear sister, +And keep you in the rear of your affection, +Out of the shot and danger of desire. +The chariest maid is prodigal enough, +If she unmask her beauty to the moon: +Virtue itself 'scapes not calumnious strokes: +The canker galls the infants of the spring, +Too oft before their buttons be disclosed, +And in the morn and liquid dew of youth +Contagious blastments are most imminent. +Be wary then; best safety lies in fear: +Youth to itself rebels, though none else near. + + + +OPHELIA +I shall the effect of this good lesson keep, +As watchman to my heart. But, good my brother, +Do not, as some ungracious pastors do, +Show me the steep and thorny way to heaven; +Whiles, like a puff'd and reckless libertine, +Himself the primrose path of dalliance treads, +And recks not his own rede. + + + +LAERTES +O, fear me not. +I stay too long: but here my father comes. +Enter POLONIUS +A double blessing is a double grace, +Occasion smiles upon a second leave. + + + +LORD POLONIUS +Yet here, Laertes! aboard, aboard, for shame! +The wind sits in the shoulder of your sail, +And you are stay'd for. There; my blessing with thee! +And these few precepts in thy memory +See thou character. Give thy thoughts no tongue, +Nor any unproportioned thought his act. +Be thou familiar, but by no means vulgar. +Those friends thou hast, and their adoption tried, +Grapple them to thy soul with hoops of steel; +But do not dull thy palm with entertainment +Of each new-hatch'd, unfledged comrade. Beware +Of entrance to a quarrel, but being in, +Bear't that the opposed may beware of thee. +Give every man thy ear, but few thy voice; +Take each man's censure, but reserve thy judgment. +Costly thy habit as thy purse can buy, +But not express'd in fancy; rich, not gaudy; +For the apparel oft proclaims the man, +And they in France of the best rank and station +Are of a most select and generous chief in that. +Neither a borrower nor a lender be; +For loan oft loses both itself and friend, +And borrowing dulls the edge of husbandry. +This above all: to thine ownself be true, +And it must follow, as the night the day, +Thou canst not then be false to any man. +Farewell: my blessing season this in thee! + + + +LAERTES +Most humbly do I take my leave, my lord. + + + +LORD POLONIUS +The time invites you; go; your servants tend. + + + +LAERTES +Farewell, Ophelia; and remember well +What I have said to you. + + + +OPHELIA +'Tis in my memory lock'd, +And you yourself shall keep the key of it. + + + +LAERTES +Farewell. + + + +Exit + + +LORD POLONIUS +What is't, Ophelia, be hath said to you? + + + +OPHELIA +So please you, something touching the Lord Hamlet. + + + +LORD POLONIUS +Marry, well bethought: +'Tis told me, he hath very oft of late +Given private time to you; and you yourself +Have of your audience been most free and bounteous: +If it be so, as so 'tis put on me, +And that in way of caution, I must tell you, +You do not understand yourself so clearly +As it behoves my daughter and your honour. +What is between you? give me up the truth. + + + +OPHELIA +He hath, my lord, of late made many tenders +Of his affection to me. + + + +LORD POLONIUS +Affection! pooh! you speak like a green girl, +Unsifted in such perilous circumstance. +Do you believe his tenders, as you call them? + + + +OPHELIA +I do not know, my lord, what I should think. + + + +LORD POLONIUS +Marry, I'll teach you: think yourself a baby; +That you have ta'en these tenders for true pay, +Which are not sterling. Tender yourself more dearly; +Or--not to crack the wind of the poor phrase, +Running it thus--you'll tender me a fool. + + + +OPHELIA +My lord, he hath importuned me with love +In honourable fashion. + + + +LORD POLONIUS +Ay, fashion you may call it; go to, go to. + + + +OPHELIA +And hath given countenance to his speech, my lord, +With almost all the holy vows of heaven. + + + +LORD POLONIUS +Ay, springes to catch woodcocks. I do know, +When the blood burns, how prodigal the soul +Lends the tongue vows: these blazes, daughter, +Giving more light than heat, extinct in both, +Even in their promise, as it is a-making, +You must not take for fire. From this time +Be somewhat scanter of your maiden presence; +Set your entreatments at a higher rate +Than a command to parley. For Lord Hamlet, +Believe so much in him, that he is young +And with a larger tether may he walk +Than may be given you: in few, Ophelia, +Do not believe his vows; for they are brokers, +Not of that dye which their investments show, +But mere implorators of unholy suits, +Breathing like sanctified and pious bawds, +The better to beguile. This is for all: +I would not, in plain terms, from this time forth, +Have you so slander any moment leisure, +As to give words or talk with the Lord Hamlet. +Look to't, I charge you: come your ways. + + + +OPHELIA +I shall obey, my lord. + + + +Exeunt + + +SCENE IV. The platform. +Enter HAMLET, HORATIO, and MARCELLUS + + +HAMLET +The air bites shrewdly; it is very cold. + + + +HORATIO +It is a nipping and an eager air. + + + +HAMLET +What hour now? + + + +HORATIO +I think it lacks of twelve. + + + +HAMLET +No, it is struck. + + + +HORATIO +Indeed? I heard it not: then it draws near the season +Wherein the spirit held his wont to walk. +A flourish of trumpets, and ordnance shot off, within +What does this mean, my lord? + + + +HAMLET +The king doth wake to-night and takes his rouse, +Keeps wassail, and the swaggering up-spring reels; +And, as he drains his draughts of Rhenish down, +The kettle-drum and trumpet thus bray out +The triumph of his pledge. + + + +HORATIO +Is it a custom? + + + +HAMLET +Ay, marry, is't: +But to my mind, though I am native here +And to the manner born, it is a custom +More honour'd in the breach than the observance. +This heavy-headed revel east and west +Makes us traduced and tax'd of other nations: +They clepe us drunkards, and with swinish phrase +Soil our addition; and indeed it takes +From our achievements, though perform'd at height, +The pith and marrow of our attribute. +So, oft it chances in particular men, +That for some vicious mole of nature in them, +As, in their birth--wherein they are not guilty, +Since nature cannot choose his origin-- +By the o'ergrowth of some complexion, +Oft breaking down the pales and forts of reason, +Or by some habit that too much o'er-leavens +The form of plausive manners, that these men, +Carrying, I say, the stamp of one defect, +Being nature's livery, or fortune's star,-- +Their virtues else--be they as pure as grace, +As infinite as man may undergo-- +Shall in the general censure take corruption +From that particular fault: the dram of eale +Doth all the noble substance of a doubt +To his own scandal. + + + +HORATIO +Look, my lord, it comes! + + + +Enter Ghost + + +HAMLET +Angels and ministers of grace defend us! +Be thou a spirit of health or goblin damn'd, +Bring with thee airs from heaven or blasts from hell, +Be thy intents wicked or charitable, +Thou comest in such a questionable shape +That I will speak to thee: I'll call thee Hamlet, +King, father, royal Dane: O, answer me! +Let me not burst in ignorance; but tell +Why thy canonized bones, hearsed in death, +Have burst their cerements; why the sepulchre, +Wherein we saw thee quietly inurn'd, +Hath oped his ponderous and marble jaws, +To cast thee up again. What may this mean, +That thou, dead corse, again in complete steel +Revisit'st thus the glimpses of the moon, +Making night hideous; and we fools of nature +So horridly to shake our disposition +With thoughts beyond the reaches of our souls? +Say, why is this? wherefore? what should we do? + + + +Ghost beckons HAMLET + + +HORATIO +It beckons you to go away with it, +As if it some impartment did desire +To you alone. + + + +MARCELLUS +Look, with what courteous action +It waves you to a more removed ground: +But do not go with it. + + + +HORATIO +No, by no means. + + + +HAMLET +It will not speak; then I will follow it. + + + +HORATIO +Do not, my lord. + + + +HAMLET +Why, what should be the fear? +I do not set my life in a pin's fee; +And for my soul, what can it do to that, +Being a thing immortal as itself? +It waves me forth again: I'll follow it. + + + +HORATIO +What if it tempt you toward the flood, my lord, +Or to the dreadful summit of the cliff +That beetles o'er his base into the sea, +And there assume some other horrible form, +Which might deprive your sovereignty of reason +And draw you into madness? think of it: +The very place puts toys of desperation, +Without more motive, into every brain +That looks so many fathoms to the sea +And hears it roar beneath. + + + +HAMLET +It waves me still. +Go on; I'll follow thee. + + + +MARCELLUS +You shall not go, my lord. + + + +HAMLET +Hold off your hands. + + + +HORATIO +Be ruled; you shall not go. + + + +HAMLET +My fate cries out, +And makes each petty artery in this body +As hardy as the Nemean lion's nerve. +Still am I call'd. Unhand me, gentlemen. +By heaven, I'll make a ghost of him that lets me! +I say, away! Go on; I'll follow thee. + + + +Exeunt Ghost and HAMLET + + +HORATIO +He waxes desperate with imagination. + + + +MARCELLUS +Let's follow; 'tis not fit thus to obey him. + + + +HORATIO +Have after. To what issue will this come? + + + +MARCELLUS +Something is rotten in the state of Denmark. + + + +HORATIO +Heaven will direct it. + + + +MARCELLUS +Nay, let's follow him. + + + +Exeunt + + +SCENE V. Another part of the platform. +Enter GHOST and HAMLET + + +HAMLET +Where wilt thou lead me? speak; I'll go no further. + + + +Ghost +Mark me. + + + +HAMLET +I will. + + + +Ghost +My hour is almost come, +When I to sulphurous and tormenting flames +Must render up myself. + + + +HAMLET +Alas, poor ghost! + + + +Ghost +Pity me not, but lend thy serious hearing +To what I shall unfold. + + + +HAMLET +Speak; I am bound to hear. + + + +Ghost +So art thou to revenge, when thou shalt hear. + + + +HAMLET +What? + + + +Ghost +I am thy father's spirit, +Doom'd for a certain term to walk the night, +And for the day confined to fast in fires, +Till the foul crimes done in my days of nature +Are burnt and purged away. But that I am forbid +To tell the secrets of my prison-house, +I could a tale unfold whose lightest word +Would harrow up thy soul, freeze thy young blood, +Make thy two eyes, like stars, start from their spheres, +Thy knotted and combined locks to part +And each particular hair to stand on end, +Like quills upon the fretful porpentine: +But this eternal blazon must not be +To ears of flesh and blood. List, list, O, list! +If thou didst ever thy dear father love-- + + + +HAMLET +O God! + + + +Ghost +Revenge his foul and most unnatural murder. + + + +HAMLET +Murder! + + + +Ghost +Murder most foul, as in the best it is; +But this most foul, strange and unnatural. + + + +HAMLET +Haste me to know't, that I, with wings as swift +As meditation or the thoughts of love, +May sweep to my revenge. + + + +Ghost +I find thee apt; +And duller shouldst thou be than the fat weed +That roots itself in ease on Lethe wharf, +Wouldst thou not stir in this. Now, Hamlet, hear: +'Tis given out that, sleeping in my orchard, +A serpent stung me; so the whole ear of Denmark +Is by a forged process of my death +Rankly abused: but know, thou noble youth, +The serpent that did sting thy father's life +Now wears his crown. + + + +HAMLET +O my prophetic soul! My uncle! + + + +Ghost +Ay, that incestuous, that adulterate beast, +With witchcraft of his wit, with traitorous gifts,-- +O wicked wit and gifts, that have the power +So to seduce!--won to his shameful lust +The will of my most seeming-virtuous queen: +O Hamlet, what a falling-off was there! +From me, whose love was of that dignity +That it went hand in hand even with the vow +I made to her in marriage, and to decline +Upon a wretch whose natural gifts were poor +To those of mine! +But virtue, as it never will be moved, +Though lewdness court it in a shape of heaven, +So lust, though to a radiant angel link'd, +Will sate itself in a celestial bed, +And prey on garbage. +But, soft! methinks I scent the morning air; +Brief let me be. Sleeping within my orchard, +My custom always of the afternoon, +Upon my secure hour thy uncle stole, +With juice of cursed hebenon in a vial, +And in the porches of my ears did pour +The leperous distilment; whose effect +Holds such an enmity with blood of man +That swift as quicksilver it courses through +The natural gates and alleys of the body, +And with a sudden vigour doth posset +And curd, like eager droppings into milk, +The thin and wholesome blood: so did it mine; +And a most instant tetter bark'd about, +Most lazar-like, with vile and loathsome crust, +All my smooth body. +Thus was I, sleeping, by a brother's hand +Of life, of crown, of queen, at once dispatch'd: +Cut off even in the blossoms of my sin, +Unhousel'd, disappointed, unanel'd, +No reckoning made, but sent to my account +With all my imperfections on my head: +O, horrible! O, horrible! most horrible! +If thou hast nature in thee, bear it not; +Let not the royal bed of Denmark be +A couch for luxury and damned incest. +But, howsoever thou pursuest this act, +Taint not thy mind, nor let thy soul contrive +Against thy mother aught: leave her to heaven +And to those thorns that in her bosom lodge, +To prick and sting her. Fare thee well at once! +The glow-worm shows the matin to be near, +And 'gins to pale his uneffectual fire: +Adieu, adieu! Hamlet, remember me. + + + +Exit + + +HAMLET +O all you host of heaven! O earth! what else? +And shall I couple hell? O, fie! Hold, hold, my heart; +And you, my sinews, grow not instant old, +But bear me stiffly up. Remember thee! +Ay, thou poor ghost, while memory holds a seat +In this distracted globe. Remember thee! +Yea, from the table of my memory +I'll wipe away all trivial fond records, +All saws of books, all forms, all pressures past, +That youth and observation copied there; +And thy commandment all alone shall live +Within the book and volume of my brain, +Unmix'd with baser matter: yes, by heaven! +O most pernicious woman! +O villain, villain, smiling, damned villain! +My tables,--meet it is I set it down, +That one may smile, and smile, and be a villain; +At least I'm sure it may be so in Denmark: +Writing +So, uncle, there you are. Now to my word; +It is 'Adieu, adieu! remember me.' +I have sworn 't. + + + +MARCELLUS +HORATIO +Within My lord, my lord,-- + + + +MARCELLUS +Within Lord Hamlet,-- + + + +HORATIO +Within Heaven secure him! + + + +HAMLET +So be it! + + + +HORATIO +Within Hillo, ho, ho, my lord! + + + +HAMLET +Hillo, ho, ho, boy! come, bird, come. + + + +Enter HORATIO and MARCELLUS + + +MARCELLUS +How is't, my noble lord? + + + +HORATIO +What news, my lord? + + + +HAMLET +O, wonderful! + + + +HORATIO +Good my lord, tell it. + + + +HAMLET +No; you'll reveal it. + + + +HORATIO +Not I, my lord, by heaven. + + + +MARCELLUS +Nor I, my lord. + + + +HAMLET +How say you, then; would heart of man once think it? +But you'll be secret? + + + +HORATIO +MARCELLUS +Ay, by heaven, my lord. + + + +HAMLET +There's ne'er a villain dwelling in all Denmark +But he's an arrant knave. + + + +HORATIO +There needs no ghost, my lord, come from the grave +To tell us this. + + + +HAMLET +Why, right; you are i' the right; +And so, without more circumstance at all, +I hold it fit that we shake hands and part: +You, as your business and desire shall point you; +For every man has business and desire, +Such as it is; and for mine own poor part, +Look you, I'll go pray. + + + +HORATIO +These are but wild and whirling words, my lord. + + + +HAMLET +I'm sorry they offend you, heartily; +Yes, 'faith heartily. + + + +HORATIO +There's no offence, my lord. + + + +HAMLET +Yes, by Saint Patrick, but there is, Horatio, +And much offence too. Touching this vision here, +It is an honest ghost, that let me tell you: +For your desire to know what is between us, +O'ermaster 't as you may. And now, good friends, +As you are friends, scholars and soldiers, +Give me one poor request. + + + +HORATIO +What is't, my lord? we will. + + + +HAMLET +Never make known what you have seen to-night. + + + +HORATIO +MARCELLUS +My lord, we will not. + + + +HAMLET +Nay, but swear't. + + + +HORATIO +In faith, +My lord, not I. + + + +MARCELLUS +Nor I, my lord, in faith. + + + +HAMLET +Upon my sword. + + + +MARCELLUS +We have sworn, my lord, already. + + + +HAMLET +Indeed, upon my sword, indeed. + + + +Ghost +Beneath Swear. + + + +HAMLET +Ah, ha, boy! say'st thou so? art thou there, +truepenny? +Come on--you hear this fellow in the cellarage-- +Consent to swear. + + + +HORATIO +Propose the oath, my lord. + + + +HAMLET +Never to speak of this that you have seen, +Swear by my sword. + + + +Ghost +Beneath Swear. + + + +HAMLET +Hic et ubique? then we'll shift our ground. +Come hither, gentlemen, +And lay your hands again upon my sword: +Never to speak of this that you have heard, +Swear by my sword. + + + +Ghost +Beneath Swear. + + + +HAMLET +Well said, old mole! canst work i' the earth so fast? +A worthy pioner! Once more remove, good friends. + + + +HORATIO +O day and night, but this is wondrous strange! + + + +HAMLET +And therefore as a stranger give it welcome. +There are more things in heaven and earth, Horatio, +Than are dreamt of in your philosophy. But come; +Here, as before, never, so help you mercy, +How strange or odd soe'er I bear myself, +As I perchance hereafter shall think meet +To put an antic disposition on, +That you, at such times seeing me, never shall, +With arms encumber'd thus, or this headshake, +Or by pronouncing of some doubtful phrase, +As 'Well, well, we know,' or 'We could, an if we would,' +Or 'If we list to speak,' or 'There be, an if they might,' +Or such ambiguous giving out, to note +That you know aught of me: this not to do, +So grace and mercy at your most need help you, Swear. + + + +Ghost +Beneath Swear. + + + +HAMLET +Rest, rest, perturbed spirit! +They swear +So, gentlemen, +With all my love I do commend me to you: +And what so poor a man as Hamlet is +May do, to express his love and friending to you, +God willing, shall not lack. Let us go in together; +And still your fingers on your lips, I pray. +The time is out of joint: O cursed spite, +That ever I was born to set it right! +Nay, come, let's go together. + + + +Exeunt + + + + +ACT II + +SCENE I. A room in POLONIUS' house. +Enter POLONIUS and REYNALDO + + +LORD POLONIUS +Give him this money and these notes, Reynaldo. + + + +REYNALDO +I will, my lord. + + + +LORD POLONIUS +You shall do marvellous wisely, good Reynaldo, +Before you visit him, to make inquire +Of his behavior. + + + +REYNALDO +My lord, I did intend it. + + + +LORD POLONIUS +Marry, well said; very well said. Look you, sir, +Inquire me first what Danskers are in Paris; +And how, and who, what means, and where they keep, +What company, at what expense; and finding +By this encompassment and drift of question +That they do know my son, come you more nearer +Than your particular demands will touch it: +Take you, as 'twere, some distant knowledge of him; +As thus, 'I know his father and his friends, +And in part him: ' do you mark this, Reynaldo? + + + +REYNALDO +Ay, very well, my lord. + + + +LORD POLONIUS +'And in part him; but' you may say 'not well: +But, if't be he I mean, he's very wild; +Addicted so and so:' and there put on him +What forgeries you please; marry, none so rank +As may dishonour him; take heed of that; +But, sir, such wanton, wild and usual slips +As are companions noted and most known +To youth and liberty. + + + +REYNALDO +As gaming, my lord. + + + +LORD POLONIUS +Ay, or drinking, fencing, swearing, quarrelling, +Drabbing: you may go so far. + + + +REYNALDO +My lord, that would dishonour him. + + + +LORD POLONIUS +'Faith, no; as you may season it in the charge +You must not put another scandal on him, +That he is open to incontinency; +That's not my meaning: but breathe his faults so quaintly +That they may seem the taints of liberty, +The flash and outbreak of a fiery mind, +A savageness in unreclaimed blood, +Of general assault. + + + +REYNALDO +But, my good lord,-- + + + +LORD POLONIUS +Wherefore should you do this? + + + +REYNALDO +Ay, my lord, +I would know that. + + + +LORD POLONIUS +Marry, sir, here's my drift; +And I believe, it is a fetch of wit: +You laying these slight sullies on my son, +As 'twere a thing a little soil'd i' the working, Mark you, +Your party in converse, him you would sound, +Having ever seen in the prenominate crimes +The youth you breathe of guilty, be assured +He closes with you in this consequence; +'Good sir,' or so, or 'friend,' or 'gentleman,' +According to the phrase or the addition +Of man and country. + + + +REYNALDO +Very good, my lord. + + + +LORD POLONIUS +And then, sir, does he this--he does--what was I +about to say? By the mass, I was about to say +something: where did I leave? + + + +REYNALDO +At 'closes in the consequence,' at 'friend or so,' +and 'gentleman.' + + + +LORD POLONIUS +At 'closes in the consequence,' ay, marry; +He closes thus: 'I know the gentleman; +I saw him yesterday, or t' other day, +Or then, or then; with such, or such; and, as you say, +There was a' gaming; there o'ertook in's rouse; +There falling out at tennis:' or perchance, +'I saw him enter such a house of sale,' +Videlicet, a brothel, or so forth. +See you now; +Your bait of falsehood takes this carp of truth: +And thus do we of wisdom and of reach, +With windlasses and with assays of bias, +By indirections find directions out: +So by my former lecture and advice, +Shall you my son. You have me, have you not? + + + +REYNALDO +My lord, I have. + + + +LORD POLONIUS +God be wi' you; fare you well. + + + +REYNALDO +Good my lord! + + + +LORD POLONIUS +Observe his inclination in yourself. + + + +REYNALDO +I shall, my lord. + + + +LORD POLONIUS +And let him ply his music. + + + +REYNALDO +Well, my lord. + + + +LORD POLONIUS +Farewell! +Exit REYNALDO +Enter OPHELIA +How now, Ophelia! what's the matter? + + + +OPHELIA +O, my lord, my lord, I have been so affrighted! + + + +LORD POLONIUS +With what, i' the name of God? + + + +OPHELIA +My lord, as I was sewing in my closet, +Lord Hamlet, with his doublet all unbraced; +No hat upon his head; his stockings foul'd, +Ungarter'd, and down-gyved to his ancle; +Pale as his shirt; his knees knocking each other; +And with a look so piteous in purport +As if he had been loosed out of hell +To speak of horrors,--he comes before me. + + + +LORD POLONIUS +Mad for thy love? + + + +OPHELIA +My lord, I do not know; +But truly, I do fear it. + + + +LORD POLONIUS +What said he? + + + +OPHELIA +He took me by the wrist and held me hard; +Then goes he to the length of all his arm; +And, with his other hand thus o'er his brow, +He falls to such perusal of my face +As he would draw it. Long stay'd he so; +At last, a little shaking of mine arm +And thrice his head thus waving up and down, +He raised a sigh so piteous and profound +As it did seem to shatter all his bulk +And end his being: that done, he lets me go: +And, with his head over his shoulder turn'd, +He seem'd to find his way without his eyes; +For out o' doors he went without their helps, +And, to the last, bended their light on me. + + + +LORD POLONIUS +Come, go with me: I will go seek the king. +This is the very ecstasy of love, +Whose violent property fordoes itself +And leads the will to desperate undertakings +As oft as any passion under heaven +That does afflict our natures. I am sorry. +What, have you given him any hard words of late? + + + +OPHELIA +No, my good lord, but, as you did command, +I did repel his fetters and denied +His access to me. + + + +LORD POLONIUS +That hath made him mad. +I am sorry that with better heed and judgment +I had not quoted him: I fear'd he did but trifle, +And meant to wreck thee; but, beshrew my jealousy! +By heaven, it is as proper to our age +To cast beyond ourselves in our opinions +As it is common for the younger sort +To lack discretion. Come, go we to the king: +This must be known; which, being kept close, might +move +More grief to hide than hate to utter love. + + + +Exeunt + + +SCENE II. A room in the castle. +Enter KING CLAUDIUS, QUEEN GERTRUDE, ROSENCRANTZ, +GUILDENSTERN, and Attendants + + +KING CLAUDIUS +Welcome, dear Rosencrantz and Guildenstern! +Moreover that we much did long to see you, +The need we have to use you did provoke +Our hasty sending. Something have you heard +Of Hamlet's transformation; so call it, +Sith nor the exterior nor the inward man +Resembles that it was. What it should be, +More than his father's death, that thus hath put him +So much from the understanding of himself, +I cannot dream of: I entreat you both, +That, being of so young days brought up with him, +And sith so neighbour'd to his youth and havior, +That you vouchsafe your rest here in our court +Some little time: so by your companies +To draw him on to pleasures, and to gather, +So much as from occasion you may glean, +Whether aught, to us unknown, afflicts him thus, +That, open'd, lies within our remedy. + + + +QUEEN GERTRUDE +Good gentlemen, he hath much talk'd of you; +And sure I am two men there are not living +To whom he more adheres. If it will please you +To show us so much gentry and good will +As to expend your time with us awhile, +For the supply and profit of our hope, +Your visitation shall receive such thanks +As fits a king's remembrance. + + + +ROSENCRANTZ +Both your majesties +Might, by the sovereign power you have of us, +Put your dread pleasures more into command +Than to entreaty. + + + +GUILDENSTERN +But we both obey, +And here give up ourselves, in the full bent +To lay our service freely at your feet, +To be commanded. + + + +KING CLAUDIUS +Thanks, Rosencrantz and gentle Guildenstern. + + + +QUEEN GERTRUDE +Thanks, Guildenstern and gentle Rosencrantz: +And I beseech you instantly to visit +My too much changed son. Go, some of you, +And bring these gentlemen where Hamlet is. + + + +GUILDENSTERN +Heavens make our presence and our practises +Pleasant and helpful to him! + + + +QUEEN GERTRUDE +Ay, amen! + + +Exeunt ROSENCRANTZ, GUILDENSTERN, and some +Attendants +Enter POLONIUS + + +LORD POLONIUS +The ambassadors from Norway, my good lord, +Are joyfully return'd. + + + +KING CLAUDIUS +Thou still hast been the father of good news. + + + +LORD POLONIUS +Have I, my lord? I assure my good liege, +I hold my duty, as I hold my soul, +Both to my God and to my gracious king: +And I do think, or else this brain of mine +Hunts not the trail of policy so sure +As it hath used to do, that I have found +The very cause of Hamlet's lunacy. + + + +KING CLAUDIUS +O, speak of that; that do I long to hear. + + + +LORD POLONIUS +Give first admittance to the ambassadors; +My news shall be the fruit to that great feast. + + + +KING CLAUDIUS +Thyself do grace to them, and bring them in. +Exit POLONIUS +He tells me, my dear Gertrude, he hath found +The head and source of all your son's distemper. + + + +QUEEN GERTRUDE +I doubt it is no other but the main; +His father's death, and our o'erhasty marriage. + + + +KING CLAUDIUS +Well, we shall sift him. +Re-enter POLONIUS, with VOLTIMAND and CORNELIUS +Welcome, my good friends! +Say, Voltimand, what from our brother Norway? + + + +VOLTIMAND +Most fair return of greetings and desires. +Upon our first, he sent out to suppress +His nephew's levies; which to him appear'd +To be a preparation 'gainst the Polack; +But, better look'd into, he truly found +It was against your highness: whereat grieved, +That so his sickness, age and impotence +Was falsely borne in hand, sends out arrests +On Fortinbras; which he, in brief, obeys; +Receives rebuke from Norway, and in fine +Makes vow before his uncle never more +To give the assay of arms against your majesty. +Whereon old Norway, overcome with joy, +Gives him three thousand crowns in annual fee, +And his commission to employ those soldiers, +So levied as before, against the Polack: +With an entreaty, herein further shown, +Giving a paper +That it might please you to give quiet pass +Through your dominions for this enterprise, +On such regards of safety and allowance +As therein are set down. + + + +KING CLAUDIUS +It likes us well; +And at our more consider'd time well read, +Answer, and think upon this business. +Meantime we thank you for your well-took labour: +Go to your rest; at night we'll feast together: +Most welcome home! + + + +Exeunt VOLTIMAND and CORNELIUS + + +LORD POLONIUS +This business is well ended. +My liege, and madam, to expostulate +What majesty should be, what duty is, +Why day is day, night night, and time is time, +Were nothing but to waste night, day and time. +Therefore, since brevity is the soul of wit, +And tediousness the limbs and outward flourishes, +I will be brief: your noble son is mad: +Mad call I it; for, to define true madness, +What is't but to be nothing else but mad? +But let that go. + + + +QUEEN GERTRUDE +More matter, with less art. + + + +LORD POLONIUS +Madam, I swear I use no art at all. +That he is mad, 'tis true: 'tis true 'tis pity; +And pity 'tis 'tis true: a foolish figure; +But farewell it, for I will use no art. +Mad let us grant him, then: and now remains +That we find out the cause of this effect, +Or rather say, the cause of this defect, +For this effect defective comes by cause: +Thus it remains, and the remainder thus. Perpend. +I have a daughter--have while she is mine-- +Who, in her duty and obedience, mark, +Hath given me this: now gather, and surmise. +Reads +'To the celestial and my soul's idol, the most +beautified Ophelia,'-- +That's an ill phrase, a vile phrase; 'beautified' is +a vile phrase: but you shall hear. Thus: +Reads +'In her excellent white bosom, these, &c.' + + + +QUEEN GERTRUDE +Came this from Hamlet to her? + + + +LORD POLONIUS +Good madam, stay awhile; I will be faithful. +Reads +'Doubt thou the stars are fire; +Doubt that the sun doth move; +Doubt truth to be a liar; +But never doubt I love. +'O dear Ophelia, I am ill at these numbers; +I have not art to reckon my groans: but that +I love thee best, O most best, believe it. Adieu. +'Thine evermore most dear lady, whilst +this machine is to him, HAMLET.' +This, in obedience, hath my daughter shown me, +And more above, hath his solicitings, +As they fell out by time, by means and place, +All given to mine ear. + + + +KING CLAUDIUS +But how hath she +Received his love? + + + +LORD POLONIUS +What do you think of me? + + + +KING CLAUDIUS +As of a man faithful and honourable. + + + +LORD POLONIUS +I would fain prove so. But what might you think, +When I had seen this hot love on the wing-- +As I perceived it, I must tell you that, +Before my daughter told me--what might you, +Or my dear majesty your queen here, think, +If I had play'd the desk or table-book, +Or given my heart a winking, mute and dumb, +Or look'd upon this love with idle sight; +What might you think? No, I went round to work, +And my young mistress thus I did bespeak: +'Lord Hamlet is a prince, out of thy star; +This must not be:' and then I precepts gave her, +That she should lock herself from his resort, +Admit no messengers, receive no tokens. +Which done, she took the fruits of my advice; +And he, repulsed--a short tale to make-- +Fell into a sadness, then into a fast, +Thence to a watch, thence into a weakness, +Thence to a lightness, and, by this declension, +Into the madness wherein now he raves, +And all we mourn for. + + + +KING CLAUDIUS +Do you think 'tis this? + + + +QUEEN GERTRUDE +It may be, very likely. + + + +LORD POLONIUS +Hath there been such a time--I'd fain know that-- +That I have positively said 'Tis so,' +When it proved otherwise? + + + +KING CLAUDIUS +Not that I know. + + + +LORD POLONIUS +Pointing to his head and shoulder +Take this from this, if this be otherwise: +If circumstances lead me, I will find +Where truth is hid, though it were hid indeed +Within the centre. + + + +KING CLAUDIUS +How may we try it further? + + + +LORD POLONIUS +You know, sometimes he walks four hours together +Here in the lobby. + + + +QUEEN GERTRUDE +So he does indeed. + + + +LORD POLONIUS +At such a time I'll loose my daughter to him: +Be you and I behind an arras then; +Mark the encounter: if he love her not +And be not from his reason fall'n thereon, +Let me be no assistant for a state, +But keep a farm and carters. + + + +KING CLAUDIUS +We will try it. + + + +QUEEN GERTRUDE +But, look, where sadly the poor wretch comes reading. + + + +LORD POLONIUS +Away, I do beseech you, both away: +I'll board him presently. +Exeunt KING CLAUDIUS, QUEEN GERTRUDE, and +Attendants +Enter HAMLET, reading +O, give me leave: +How does my good Lord Hamlet? + + + +HAMLET +Well, God-a-mercy. + + + +LORD POLONIUS +Do you know me, my lord? + + + +HAMLET +Excellent well; you are a fishmonger. + + + +LORD POLONIUS +Not I, my lord. + + + +HAMLET +Then I would you were so honest a man. + + + +LORD POLONIUS +Honest, my lord! + + + +HAMLET +Ay, sir; to be honest, as this world goes, is to be +one man picked out of ten thousand. + + + +LORD POLONIUS +That's very true, my lord. + + + +HAMLET +For if the sun breed maggots in a dead dog, being a +god kissing carrion,--Have you a daughter? + + + +LORD POLONIUS +I have, my lord. + + + +HAMLET +Let her not walk i' the sun: conception is a +blessing: but not as your daughter may conceive. +Friend, look to 't. + + + +LORD POLONIUS +Aside How say you by that? Still harping on my +daughter: yet he knew me not at first; he said I +was a fishmonger: he is far gone, far gone: and +truly in my youth I suffered much extremity for +love; very near this. I'll speak to him again. +What do you read, my lord? + + + +HAMLET +Words, words, words. + + + +LORD POLONIUS +What is the matter, my lord? + + + +HAMLET +Between who? + + + +LORD POLONIUS +I mean, the matter that you read, my lord. + + + +HAMLET +Slanders, sir: for the satirical rogue says here +that old men have grey beards, that their faces are +wrinkled, their eyes purging thick amber and +plum-tree gum and that they have a plentiful lack of +wit, together with most weak hams: all which, sir, +though I most powerfully and potently believe, yet +I hold it not honesty to have it thus set down, for +yourself, sir, should be old as I am, if like a crab +you could go backward. + + + +LORD POLONIUS +Aside Though this be madness, yet there is method +in 't. Will you walk out of the air, my lord? + + + +HAMLET +Into my grave. + + + +LORD POLONIUS +Indeed, that is out o' the air. +Aside +How pregnant sometimes his replies are! a happiness +that often madness hits on, which reason and sanity +could not so prosperously be delivered of. I will +leave him, and suddenly contrive the means of +meeting between him and my daughter.--My honourable +lord, I will most humbly take my leave of you. + + + +HAMLET +You cannot, sir, take from me any thing that I will +more willingly part withal: except my life, except +my life, except my life. + + + +LORD POLONIUS +Fare you well, my lord. + + + +HAMLET +These tedious old fools! + + + +Enter ROSENCRANTZ and GUILDENSTERN + + +LORD POLONIUS +You go to seek the Lord Hamlet; there he is. + + + +ROSENCRANTZ +To POLONIUS God save you, sir! + + + +Exit POLONIUS + + +GUILDENSTERN +My honoured lord! + + + +ROSENCRANTZ +My most dear lord! + + + +HAMLET +My excellent good friends! How dost thou, +Guildenstern? Ah, Rosencrantz! Good lads, how do ye both? + + + +ROSENCRANTZ +As the indifferent children of the earth. + + + +GUILDENSTERN +Happy, in that we are not over-happy; +On fortune's cap we are not the very button. + + + +HAMLET +Nor the soles of her shoe? + + + +ROSENCRANTZ +Neither, my lord. + + + +HAMLET +Then you live about her waist, or in the middle of +her favours? + + + +GUILDENSTERN +'Faith, her privates we. + + + +HAMLET +In the secret parts of fortune? O, most true; she +is a strumpet. What's the news? + + + +ROSENCRANTZ +None, my lord, but that the world's grown honest. + + + +HAMLET +Then is doomsday near: but your news is not true. +Let me question more in particular: what have you, +my good friends, deserved at the hands of fortune, +that she sends you to prison hither? + + + +GUILDENSTERN +Prison, my lord! + + + +HAMLET +Denmark's a prison. + + + +ROSENCRANTZ +Then is the world one. + + + +HAMLET +A goodly one; in which there are many confines, +wards and dungeons, Denmark being one o' the worst. + + + +ROSENCRANTZ +We think not so, my lord. + + + +HAMLET +Why, then, 'tis none to you; for there is nothing +either good or bad, but thinking makes it so: to me +it is a prison. + + + +ROSENCRANTZ +Why then, your ambition makes it one; 'tis too +narrow for your mind. + + + +HAMLET +O God, I could be bounded in a nut shell and count +myself a king of infinite space, were it not that I +have bad dreams. + + + +GUILDENSTERN +Which dreams indeed are ambition, for the very +substance of the ambitious is merely the shadow of a dream. + + + +HAMLET +A dream itself is but a shadow. + + + +ROSENCRANTZ +Truly, and I hold ambition of so airy and light a +quality that it is but a shadow's shadow. + + + +HAMLET +Then are our beggars bodies, and our monarchs and +outstretched heroes the beggars' shadows. Shall we +to the court? for, by my fay, I cannot reason. + + + +ROSENCRANTZ +GUILDENSTERN +We'll wait upon you. + + + +HAMLET +No such matter: I will not sort you with the rest +of my servants, for, to speak to you like an honest +man, I am most dreadfully attended. But, in the +beaten way of friendship, what make you at Elsinore? + + + +ROSENCRANTZ +To visit you, my lord; no other occasion. + + + +HAMLET +Beggar that I am, I am even poor in thanks; but I +thank you: and sure, dear friends, my thanks are +too dear a halfpenny. Were you not sent for? Is it +your own inclining? Is it a free visitation? Come, +deal justly with me: come, come; nay, speak. + + + +GUILDENSTERN +What should we say, my lord? + + + +HAMLET +Why, any thing, but to the purpose. You were sent +for; and there is a kind of confession in your looks +which your modesties have not craft enough to colour: +I know the good king and queen have sent for you. + + + +ROSENCRANTZ +To what end, my lord? + + + +HAMLET +That you must teach me. But let me conjure you, by +the rights of our fellowship, by the consonancy of +our youth, by the obligation of our ever-preserved +love, and by what more dear a better proposer could +charge you withal, be even and direct with me, +whether you were sent for, or no? + + + +ROSENCRANTZ +Aside to GUILDENSTERN What say you? + + + +HAMLET +Aside Nay, then, I have an eye of you.--If you +love me, hold not off. + + + +GUILDENSTERN +My lord, we were sent for. + + + +HAMLET +I will tell you why; so shall my anticipation +prevent your discovery, and your secrecy to the king +and queen moult no feather. I have of late--but +wherefore I know not--lost all my mirth, forgone all +custom of exercises; and indeed it goes so heavily +with my disposition that this goodly frame, the +earth, seems to me a sterile promontory, this most +excellent canopy, the air, look you, this brave +o'erhanging firmament, this majestical roof fretted +with golden fire, why, it appears no other thing to +me than a foul and pestilent congregation of vapours. +What a piece of work is a man! how noble in reason! +how infinite in faculty! in form and moving how +express and admirable! in action how like an angel! +in apprehension how like a god! the beauty of the +world! the paragon of animals! And yet, to me, +what is this quintessence of dust? man delights not +me: no, nor woman neither, though by your smiling +you seem to say so. + + + +ROSENCRANTZ +My lord, there was no such stuff in my thoughts. + + + +HAMLET +Why did you laugh then, when I said 'man delights not me'? + + + +ROSENCRANTZ +To think, my lord, if you delight not in man, what +lenten entertainment the players shall receive from +you: we coted them on the way; and hither are they +coming, to offer you service. + + + +HAMLET +He that plays the king shall be welcome; his majesty +shall have tribute of me; the adventurous knight +shall use his foil and target; the lover shall not +sigh gratis; the humourous man shall end his part +in peace; the clown shall make those laugh whose +lungs are tickled o' the sere; and the lady shall +say her mind freely, or the blank verse shall halt +for't. What players are they? + + + +ROSENCRANTZ +Even those you were wont to take delight in, the +tragedians of the city. + + + +HAMLET +How chances it they travel? their residence, both +in reputation and profit, was better both ways. + + + +ROSENCRANTZ +I think their inhibition comes by the means of the +late innovation. + + + +HAMLET +Do they hold the same estimation they did when I was +in the city? are they so followed? + + + +ROSENCRANTZ +No, indeed, are they not. + + + +HAMLET +How comes it? do they grow rusty? + + + +ROSENCRANTZ +Nay, their endeavour keeps in the wonted pace: but +there is, sir, an aery of children, little eyases, +that cry out on the top of question, and are most +tyrannically clapped for't: these are now the +fashion, and so berattle the common stages--so they +call them--that many wearing rapiers are afraid of +goose-quills and dare scarce come thither. + + + +HAMLET +What, are they children? who maintains 'em? how are +they escoted? Will they pursue the quality no +longer than they can sing? will they not say +afterwards, if they should grow themselves to common +players--as it is most like, if their means are no +better--their writers do them wrong, to make them +exclaim against their own succession? + + + +ROSENCRANTZ +'Faith, there has been much to do on both sides; and +the nation holds it no sin to tarre them to +controversy: there was, for a while, no money bid +for argument, unless the poet and the player went to +cuffs in the question. + + + +HAMLET +Is't possible? + + + +GUILDENSTERN +O, there has been much throwing about of brains. + + + +HAMLET +Do the boys carry it away? + + + +ROSENCRANTZ +Ay, that they do, my lord; Hercules and his load too. + + + +HAMLET +It is not very strange; for mine uncle is king of +Denmark, and those that would make mows at him while +my father lived, give twenty, forty, fifty, an +hundred ducats a-piece for his picture in little. +'Sblood, there is something in this more than +natural, if philosophy could find it out. + + + +Flourish of trumpets within + + +GUILDENSTERN +There are the players. + + + +HAMLET +Gentlemen, you are welcome to Elsinore. Your hands, +come then: the appurtenance of welcome is fashion +and ceremony: let me comply with you in this garb, +lest my extent to the players, which, I tell you, +must show fairly outward, should more appear like +entertainment than yours. You are welcome: but my +uncle-father and aunt-mother are deceived. + + + +GUILDENSTERN +In what, my dear lord? + + + +HAMLET +I am but mad north-north-west: when the wind is +southerly I know a hawk from a handsaw. + + + +Enter POLONIUS + + +LORD POLONIUS +Well be with you, gentlemen! + + + +HAMLET +Hark you, Guildenstern; and you too: at each ear a +hearer: that great baby you see there is not yet +out of his swaddling-clouts. + + + +ROSENCRANTZ +Happily he's the second time come to them; for they +say an old man is twice a child. + + + +HAMLET +I will prophesy he comes to tell me of the players; +mark it. You say right, sir: o' Monday morning; +'twas so indeed. + + + +LORD POLONIUS +My lord, I have news to tell you. + + + +HAMLET +My lord, I have news to tell you. +When Roscius was an actor in Rome,-- + + + +LORD POLONIUS +The actors are come hither, my lord. + + + +HAMLET +Buz, buz! + + + +LORD POLONIUS +Upon mine honour,-- + + + +HAMLET +Then came each actor on his ass,-- + + + +LORD POLONIUS +The best actors in the world, either for tragedy, +comedy, history, pastoral, pastoral-comical, +historical-pastoral, tragical-historical, tragical- +comical-historical-pastoral, scene individable, or +poem unlimited: Seneca cannot be too heavy, nor +Plautus too light. For the law of writ and the +liberty, these are the only men. + + + +HAMLET +O Jephthah, judge of Israel, what a treasure hadst thou! + + + +LORD POLONIUS +What a treasure had he, my lord? + + + +HAMLET +Why, +'One fair daughter and no more, +The which he loved passing well.' + + + +LORD POLONIUS +Aside Still on my daughter. + + + +HAMLET +Am I not i' the right, old Jephthah? + + + +LORD POLONIUS +If you call me Jephthah, my lord, I have a daughter +that I love passing well. + + + +HAMLET +Nay, that follows not. + + + +LORD POLONIUS +What follows, then, my lord? + + + +HAMLET +Why, +'As by lot, God wot,' +and then, you know, +'It came to pass, as most like it was,'-- +the first row of the pious chanson will show you +more; for look, where my abridgement comes. +Enter four or five Players +You are welcome, masters; welcome, all. I am glad +to see thee well. Welcome, good friends. O, my old +friend! thy face is valenced since I saw thee last: +comest thou to beard me in Denmark? What, my young +lady and mistress! By'r lady, your ladyship is +nearer to heaven than when I saw you last, by the +altitude of a chopine. Pray God, your voice, like +apiece of uncurrent gold, be not cracked within the +ring. Masters, you are all welcome. We'll e'en +to't like French falconers, fly at any thing we see: +we'll have a speech straight: come, give us a taste +of your quality; come, a passionate speech. + + + +First Player +What speech, my lord? + + + +HAMLET +I heard thee speak me a speech once, but it was +never acted; or, if it was, not above once; for the +play, I remember, pleased not the million; 'twas +caviare to the general: but it was--as I received +it, and others, whose judgments in such matters +cried in the top of mine--an excellent play, well +digested in the scenes, set down with as much +modesty as cunning. I remember, one said there +were no sallets in the lines to make the matter +savoury, nor no matter in the phrase that might +indict the author of affectation; but called it an +honest method, as wholesome as sweet, and by very +much more handsome than fine. One speech in it I +chiefly loved: 'twas Aeneas' tale to Dido; and +thereabout of it especially, where he speaks of +Priam's slaughter: if it live in your memory, begin +at this line: let me see, let me see-- +'The rugged Pyrrhus, like the Hyrcanian beast,'-- +it is not so:--it begins with Pyrrhus:-- +'The rugged Pyrrhus, he whose sable arms, +Black as his purpose, did the night resemble +When he lay couched in the ominous horse, +Hath now this dread and black complexion smear'd +With heraldry more dismal; head to foot +Now is he total gules; horridly trick'd +With blood of fathers, mothers, daughters, sons, +Baked and impasted with the parching streets, +That lend a tyrannous and damned light +To their lord's murder: roasted in wrath and fire, +And thus o'er-sized with coagulate gore, +With eyes like carbuncles, the hellish Pyrrhus +Old grandsire Priam seeks.' +So, proceed you. + + + +LORD POLONIUS +'Fore God, my lord, well spoken, with good accent and +good discretion. + + + +First Player +'Anon he finds him +Striking too short at Greeks; his antique sword, +Rebellious to his arm, lies where it falls, +Repugnant to command: unequal match'd, +Pyrrhus at Priam drives; in rage strikes wide; +But with the whiff and wind of his fell sword +The unnerved father falls. Then senseless Ilium, +Seeming to feel this blow, with flaming top +Stoops to his base, and with a hideous crash +Takes prisoner Pyrrhus' ear: for, lo! his sword, +Which was declining on the milky head +Of reverend Priam, seem'd i' the air to stick: +So, as a painted tyrant, Pyrrhus stood, +And like a neutral to his will and matter, +Did nothing. +But, as we often see, against some storm, +A silence in the heavens, the rack stand still, +The bold winds speechless and the orb below +As hush as death, anon the dreadful thunder +Doth rend the region, so, after Pyrrhus' pause, +Aroused vengeance sets him new a-work; +And never did the Cyclops' hammers fall +On Mars's armour forged for proof eterne +With less remorse than Pyrrhus' bleeding sword +Now falls on Priam. +Out, out, thou strumpet, Fortune! All you gods, +In general synod 'take away her power; +Break all the spokes and fellies from her wheel, +And bowl the round nave down the hill of heaven, +As low as to the fiends!' + + + +LORD POLONIUS +This is too long. + + + +HAMLET +It shall to the barber's, with your beard. Prithee, +say on: he's for a jig or a tale of bawdry, or he +sleeps: say on: come to Hecuba. + + + +First Player +'But who, O, who had seen the mobled queen--' + + + +HAMLET +'The mobled queen?' + + + +LORD POLONIUS +That's good; 'mobled queen' is good. + + + +First Player +'Run barefoot up and down, threatening the flames +With bisson rheum; a clout upon that head +Where late the diadem stood, and for a robe, +About her lank and all o'er-teemed loins, +A blanket, in the alarm of fear caught up; +Who this had seen, with tongue in venom steep'd, +'Gainst Fortune's state would treason have +pronounced: +But if the gods themselves did see her then +When she saw Pyrrhus make malicious sport +In mincing with his sword her husband's limbs, +The instant burst of clamour that she made, +Unless things mortal move them not at all, +Would have made milch the burning eyes of heaven, +And passion in the gods.' + + + +LORD POLONIUS +Look, whether he has not turned his colour and has +tears in's eyes. Pray you, no more. + + + +HAMLET +'Tis well: I'll have thee speak out the rest soon. +Good my lord, will you see the players well +bestowed? Do you hear, let them be well used; for +they are the abstract and brief chronicles of the +time: after your death you were better have a bad +epitaph than their ill report while you live. + + + +LORD POLONIUS +My lord, I will use them according to their desert. + + + +HAMLET +God's bodykins, man, much better: use every man +after his desert, and who should 'scape whipping? +Use them after your own honour and dignity: the less +they deserve, the more merit is in your bounty. +Take them in. + + + +LORD POLONIUS +Come, sirs. + + + +HAMLET +Follow him, friends: we'll hear a play to-morrow. +Exit POLONIUS with all the Players but the First +Dost thou hear me, old friend; can you play the +Murder of Gonzago? + + + +First Player +Ay, my lord. + + + +HAMLET +We'll ha't to-morrow night. You could, for a need, +study a speech of some dozen or sixteen lines, which +I would set down and insert in't, could you not? + + + +First Player +Ay, my lord. + + + +HAMLET +Very well. Follow that lord; and look you mock him +not. +Exit First Player +My good friends, I'll leave you till night: you are +welcome to Elsinore. + + + +ROSENCRANTZ +Good my lord! + + + +HAMLET +Ay, so, God be wi' ye; +Exeunt ROSENCRANTZ and GUILDENSTERN +Now I am alone. +O, what a rogue and peasant slave am I! +Is it not monstrous that this player here, +But in a fiction, in a dream of passion, +Could force his soul so to his own conceit +That from her working all his visage wann'd, +Tears in his eyes, distraction in's aspect, +A broken voice, and his whole function suiting +With forms to his conceit? and all for nothing! +For Hecuba! +What's Hecuba to him, or he to Hecuba, +That he should weep for her? What would he do, +Had he the motive and the cue for passion +That I have? He would drown the stage with tears +And cleave the general ear with horrid speech, +Make mad the guilty and appal the free, +Confound the ignorant, and amaze indeed +The very faculties of eyes and ears. Yet I, +A dull and muddy-mettled rascal, peak, +Like John-a-dreams, unpregnant of my cause, +And can say nothing; no, not for a king, +Upon whose property and most dear life +A damn'd defeat was made. Am I a coward? +Who calls me villain? breaks my pate across? +Plucks off my beard, and blows it in my face? +Tweaks me by the nose? gives me the lie i' the throat, +As deep as to the lungs? who does me this? +Ha! +'Swounds, I should take it: for it cannot be +But I am pigeon-liver'd and lack gall +To make oppression bitter, or ere this +I should have fatted all the region kites +With this slave's offal: bloody, bawdy villain! +Remorseless, treacherous, lecherous, kindless villain! +O, vengeance! +Why, what an ass am I! This is most brave, +That I, the son of a dear father murder'd, +Prompted to my revenge by heaven and hell, +Must, like a whore, unpack my heart with words, +And fall a-cursing, like a very drab, +A scullion! +Fie upon't! foh! About, my brain! I have heard +That guilty creatures sitting at a play +Have by the very cunning of the scene +Been struck so to the soul that presently +They have proclaim'd their malefactions; +For murder, though it have no tongue, will speak +With most miraculous organ. I'll have these players +Play something like the murder of my father +Before mine uncle: I'll observe his looks; +I'll tent him to the quick: if he but blench, +I know my course. The spirit that I have seen +May be the devil: and the devil hath power +To assume a pleasing shape; yea, and perhaps +Out of my weakness and my melancholy, +As he is very potent with such spirits, +Abuses me to damn me: I'll have grounds +More relative than this: the play 's the thing +Wherein I'll catch the conscience of the king. + + + +Exit + + + + +ACT III + +SCENE I. A room in the castle. +Enter KING CLAUDIUS, QUEEN GERTRUDE, POLONIUS, +OPHELIA, ROSENCRANTZ, and GUILDENSTERN + + +KING CLAUDIUS +And can you, by no drift of circumstance, +Get from him why he puts on this confusion, +Grating so harshly all his days of quiet +With turbulent and dangerous lunacy? + + + +ROSENCRANTZ +He does confess he feels himself distracted; +But from what cause he will by no means speak. + + + +GUILDENSTERN +Nor do we find him forward to be sounded, +But, with a crafty madness, keeps aloof, +When we would bring him on to some confession +Of his true state. + + + +QUEEN GERTRUDE +Did he receive you well? + + + +ROSENCRANTZ +Most like a gentleman. + + + +GUILDENSTERN +But with much forcing of his disposition. + + + +ROSENCRANTZ +Niggard of question; but, of our demands, +Most free in his reply. + + + +QUEEN GERTRUDE +Did you assay him? +To any pastime? + + + +ROSENCRANTZ +Madam, it so fell out, that certain players +We o'er-raught on the way: of these we told him; +And there did seem in him a kind of joy +To hear of it: they are about the court, +And, as I think, they have already order +This night to play before him. + + + +LORD POLONIUS +'Tis most true: +And he beseech'd me to entreat your majesties +To hear and see the matter. + + + +KING CLAUDIUS +With all my heart; and it doth much content me +To hear him so inclined. +Good gentlemen, give him a further edge, +And drive his purpose on to these delights. + + + +ROSENCRANTZ +We shall, my lord. + + + +Exeunt ROSENCRANTZ and GUILDENSTERN + + +KING CLAUDIUS +Sweet Gertrude, leave us too; +For we have closely sent for Hamlet hither, +That he, as 'twere by accident, may here +Affront Ophelia: +Her father and myself, lawful espials, +Will so bestow ourselves that, seeing, unseen, +We may of their encounter frankly judge, +And gather by him, as he is behaved, +If 't be the affliction of his love or no +That thus he suffers for. + + + +QUEEN GERTRUDE +I shall obey you. +And for your part, Ophelia, I do wish +That your good beauties be the happy cause +Of Hamlet's wildness: so shall I hope your virtues +Will bring him to his wonted way again, +To both your honours. + + + +OPHELIA +Madam, I wish it may. + + + +Exit QUEEN GERTRUDE + + +LORD POLONIUS +Ophelia, walk you here. Gracious, so please you, +We will bestow ourselves. +To OPHELIA +Read on this book; +That show of such an exercise may colour +Your loneliness. We are oft to blame in this,-- +'Tis too much proved--that with devotion's visage +And pious action we do sugar o'er +The devil himself. + + + +KING CLAUDIUS +Aside O, 'tis too true! +How smart a lash that speech doth give my conscience! +The harlot's cheek, beautied with plastering art, +Is not more ugly to the thing that helps it +Than is my deed to my most painted word: +O heavy burthen! + + + +LORD POLONIUS +I hear him coming: let's withdraw, my lord. + + +Exeunt KING CLAUDIUS and POLONIUS +Enter HAMLET + + +HAMLET +To be, or not to be: that is the question: +Whether 'tis nobler in the mind to suffer +The slings and arrows of outrageous fortune, +Or to take arms against a sea of troubles, +And by opposing end them? To die: to sleep; +No more; and by a sleep to say we end +The heart-ache and the thousand natural shocks +That flesh is heir to, 'tis a consummation +Devoutly to be wish'd. To die, to sleep; +To sleep: perchance to dream: ay, there's the rub; +For in that sleep of death what dreams may come +When we have shuffled off this mortal coil, +Must give us pause: there's the respect +That makes calamity of so long life; +For who would bear the whips and scorns of time, +The oppressor's wrong, the proud man's contumely, +The pangs of despised love, the law's delay, +The insolence of office and the spurns +That patient merit of the unworthy takes, +When he himself might his quietus make +With a bare bodkin? who would fardels bear, +To grunt and sweat under a weary life, +But that the dread of something after death, +The undiscover'd country from whose bourn +No traveller returns, puzzles the will +And makes us rather bear those ills we have +Than fly to others that we know not of? +Thus conscience does make cowards of us all; +And thus the native hue of resolution +Is sicklied o'er with the pale cast of thought, +And enterprises of great pith and moment +With this regard their currents turn awry, +And lose the name of action.--Soft you now! +The fair Ophelia! Nymph, in thy orisons +Be all my sins remember'd. + + + +OPHELIA +Good my lord, +How does your honour for this many a day? + + + +HAMLET +I humbly thank you; well, well, well. + + + +OPHELIA +My lord, I have remembrances of yours, +That I have longed long to re-deliver; +I pray you, now receive them. + + + +HAMLET +No, not I; +I never gave you aught. + + + +OPHELIA +My honour'd lord, you know right well you did; +And, with them, words of so sweet breath composed +As made the things more rich: their perfume lost, +Take these again; for to the noble mind +Rich gifts wax poor when givers prove unkind. +There, my lord. + + + +HAMLET +Ha, ha! are you honest? + + + +OPHELIA +My lord? + + + +HAMLET +Are you fair? + + + +OPHELIA +What means your lordship? + + + +HAMLET +That if you be honest and fair, your honesty should +admit no discourse to your beauty. + + + +OPHELIA +Could beauty, my lord, have better commerce than +with honesty? + + + +HAMLET +Ay, truly; for the power of beauty will sooner +transform honesty from what it is to a bawd than the +force of honesty can translate beauty into his +likeness: this was sometime a paradox, but now the +time gives it proof. I did love you once. + + + +OPHELIA +Indeed, my lord, you made me believe so. + + + +HAMLET +You should not have believed me; for virtue cannot +so inoculate our old stock but we shall relish of +it: I loved you not. + + + +OPHELIA +I was the more deceived. + + + +HAMLET +Get thee to a nunnery: why wouldst thou be a +breeder of sinners? I am myself indifferent honest; +but yet I could accuse me of such things that it +were better my mother had not borne me: I am very +proud, revengeful, ambitious, with more offences at +my beck than I have thoughts to put them in, +imagination to give them shape, or time to act them +in. What should such fellows as I do crawling +between earth and heaven? We are arrant knaves, +all; believe none of us. Go thy ways to a nunnery. +Where's your father? + + + +OPHELIA +At home, my lord. + + + +HAMLET +Let the doors be shut upon him, that he may play the +fool no where but in's own house. Farewell. + + + +OPHELIA +O, help him, you sweet heavens! + + + +HAMLET +If thou dost marry, I'll give thee this plague for +thy dowry: be thou as chaste as ice, as pure as +snow, thou shalt not escape calumny. Get thee to a +nunnery, go: farewell. Or, if thou wilt needs +marry, marry a fool; for wise men know well enough +what monsters you make of them. To a nunnery, go, +and quickly too. Farewell. + + + +OPHELIA +O heavenly powers, restore him! + + + +HAMLET +I have heard of your paintings too, well enough; God +has given you one face, and you make yourselves +another: you jig, you amble, and you lisp, and +nick-name God's creatures, and make your wantonness +your ignorance. Go to, I'll no more on't; it hath +made me mad. I say, we will have no more marriages: +those that are married already, all but one, shall +live; the rest shall keep as they are. To a +nunnery, go. + + + +Exit + + +OPHELIA +O, what a noble mind is here o'erthrown! +The courtier's, soldier's, scholar's, eye, tongue, sword; +The expectancy and rose of the fair state, +The glass of fashion and the mould of form, +The observed of all observers, quite, quite down! +And I, of ladies most deject and wretched, +That suck'd the honey of his music vows, +Now see that noble and most sovereign reason, +Like sweet bells jangled, out of tune and harsh; +That unmatch'd form and feature of blown youth +Blasted with ecstasy: O, woe is me, +To have seen what I have seen, see what I see! + + + +Re-enter KING CLAUDIUS and POLONIUS + + +KING CLAUDIUS +Love! his affections do not that way tend; +Nor what he spake, though it lack'd form a little, +Was not like madness. There's something in his soul, +O'er which his melancholy sits on brood; +And I do doubt the hatch and the disclose +Will be some danger: which for to prevent, +I have in quick determination +Thus set it down: he shall with speed to England, +For the demand of our neglected tribute +Haply the seas and countries different +With variable objects shall expel +This something-settled matter in his heart, +Whereon his brains still beating puts him thus +From fashion of himself. What think you on't? + + + +LORD POLONIUS +It shall do well: but yet do I believe +The origin and commencement of his grief +Sprung from neglected love. How now, Ophelia! +You need not tell us what Lord Hamlet said; +We heard it all. My lord, do as you please; +But, if you hold it fit, after the play +Let his queen mother all alone entreat him +To show his grief: let her be round with him; +And I'll be placed, so please you, in the ear +Of all their conference. If she find him not, +To England send him, or confine him where +Your wisdom best shall think. + + + +KING CLAUDIUS +It shall be so: +Madness in great ones must not unwatch'd go. + + + +Exeunt + + +SCENE II. A hall in the castle. +Enter HAMLET and Players + + +HAMLET +Speak the speech, I pray you, as I pronounced it to +you, trippingly on the tongue: but if you mouth it, +as many of your players do, I had as lief the +town-crier spoke my lines. Nor do not saw the air +too much with your hand, thus, but use all gently; +for in the very torrent, tempest, and, as I may say, +the whirlwind of passion, you must acquire and beget +a temperance that may give it smoothness. O, it +offends me to the soul to hear a robustious +periwig-pated fellow tear a passion to tatters, to +very rags, to split the ears of the groundlings, who +for the most part are capable of nothing but +inexplicable dumbshows and noise: I would have such +a fellow whipped for o'erdoing Termagant; it +out-herods Herod: pray you, avoid it. + + + +First Player +I warrant your honour. + + + +HAMLET +Be not too tame neither, but let your own discretion +be your tutor: suit the action to the word, the +word to the action; with this special o'erstep not +the modesty of nature: for any thing so overdone is +from the purpose of playing, whose end, both at the +first and now, was and is, to hold, as 'twere, the +mirror up to nature; to show virtue her own feature, +scorn her own image, and the very age and body of +the time his form and pressure. Now this overdone, +or come tardy off, though it make the unskilful +laugh, cannot but make the judicious grieve; the +censure of the which one must in your allowance +o'erweigh a whole theatre of others. O, there be +players that I have seen play, and heard others +praise, and that highly, not to speak it profanely, +that, neither having the accent of Christians nor +the gait of Christian, pagan, nor man, have so +strutted and bellowed that I have thought some of +nature's journeymen had made men and not made them +well, they imitated humanity so abominably. + + + +First Player +I hope we have reformed that indifferently with us, +sir. + + + +HAMLET +O, reform it altogether. And let those that play +your clowns speak no more than is set down for them; +for there be of them that will themselves laugh, to +set on some quantity of barren spectators to laugh +too; though, in the mean time, some necessary +question of the play be then to be considered: +that's villanous, and shows a most pitiful ambition +in the fool that uses it. Go, make you ready. +Exeunt Players +Enter POLONIUS, ROSENCRANTZ, and GUILDENSTERN +How now, my lord! I will the king hear this piece of work? + + + +LORD POLONIUS +And the queen too, and that presently. + + + +HAMLET +Bid the players make haste. +Exit POLONIUS +Will you two help to hasten them? + + + +ROSENCRANTZ +GUILDENSTERN +We will, my lord. + + +Exeunt ROSENCRANTZ and GUILDENSTERN + + +HAMLET +What ho! Horatio! + + + +Enter HORATIO + + +HORATIO +Here, sweet lord, at your service. + + + +HAMLET +Horatio, thou art e'en as just a man +As e'er my conversation coped withal. + + + +HORATIO +O, my dear lord,-- + + + +HAMLET +Nay, do not think I flatter; +For what advancement may I hope from thee +That no revenue hast but thy good spirits, +To feed and clothe thee? Why should the poor be flatter'd? +No, let the candied tongue lick absurd pomp, +And crook the pregnant hinges of the knee +Where thrift may follow fawning. Dost thou hear? +Since my dear soul was mistress of her choice +And could of men distinguish, her election +Hath seal'd thee for herself; for thou hast been +As one, in suffering all, that suffers nothing, +A man that fortune's buffets and rewards +Hast ta'en with equal thanks: and blest are those +Whose blood and judgment are so well commingled, +That they are not a pipe for fortune's finger +To sound what stop she please. Give me that man +That is not passion's slave, and I will wear him +In my heart's core, ay, in my heart of heart, +As I do thee.--Something too much of this.-- +There is a play to-night before the king; +One scene of it comes near the circumstance +Which I have told thee of my father's death: +I prithee, when thou seest that act afoot, +Even with the very comment of thy soul +Observe mine uncle: if his occulted guilt +Do not itself unkennel in one speech, +It is a damned ghost that we have seen, +And my imaginations are as foul +As Vulcan's stithy. Give him heedful note; +For I mine eyes will rivet to his face, +And after we will both our judgments join +In censure of his seeming. + + + +HORATIO +Well, my lord: +If he steal aught the whilst this play is playing, +And 'scape detecting, I will pay the theft. + + + +HAMLET +They are coming to the play; I must be idle: +Get you a place. + + + +Danish march. A flourish. Enter KING CLAUDIUS, +QUEEN GERTRUDE, POLONIUS, OPHELIA, ROSENCRANTZ, +GUILDENSTERN, and others + + +KING CLAUDIUS +How fares our cousin Hamlet? + + + +HAMLET +Excellent, i' faith; of the chameleon's dish: I eat +the air, promise-crammed: you cannot feed capons so. + + + +KING CLAUDIUS +I have nothing with this answer, Hamlet; these words +are not mine. + + + +HAMLET +No, nor mine now. +To POLONIUS +My lord, you played once i' the university, you say? + + + +LORD POLONIUS +That did I, my lord; and was accounted a good actor. + + + +HAMLET +What did you enact? + + + +LORD POLONIUS +I did enact Julius Caesar: I was killed i' the +Capitol; Brutus killed me. + + + +HAMLET +It was a brute part of him to kill so capital a calf +there. Be the players ready? + + + +ROSENCRANTZ +Ay, my lord; they stay upon your patience. + + + +QUEEN GERTRUDE +Come hither, my dear Hamlet, sit by me. + + + +HAMLET +No, good mother, here's metal more attractive. + + + +LORD POLONIUS +To KING CLAUDIUS O, ho! do you mark that? + + + +HAMLET +Lady, shall I lie in your lap? + + + +Lying down at OPHELIA's feet + + +OPHELIA +No, my lord. + + + +HAMLET +I mean, my head upon your lap? + + + +OPHELIA +Ay, my lord. + + + +HAMLET +Do you think I meant country matters? + + + +OPHELIA +I think nothing, my lord. + + + +HAMLET +That's a fair thought to lie between maids' legs. + + + +OPHELIA +What is, my lord? + + + +HAMLET +Nothing. + + + +OPHELIA +You are merry, my lord. + + + +HAMLET +Who, I? + + + +OPHELIA +Ay, my lord. + + + +HAMLET +O God, your only jig-maker. What should a man do +but be merry? for, look you, how cheerfully my +mother looks, and my father died within these two hours. + + + +OPHELIA +Nay, 'tis twice two months, my lord. + + + +HAMLET +So long? Nay then, let the devil wear black, for +I'll have a suit of sables. O heavens! die two +months ago, and not forgotten yet? Then there's +hope a great man's memory may outlive his life half +a year: but, by'r lady, he must build churches, +then; or else shall he suffer not thinking on, with +the hobby-horse, whose epitaph is 'For, O, for, O, +the hobby-horse is forgot.' +Hautboys play. The dumb-show enters + + +Enter a King and a Queen very lovingly; the Queen +embracing him, and he her. She kneels, and makes +show of protestation unto him. He takes her up, +and declines his head upon her neck: lays him down +upon a bank of flowers: she, seeing him asleep, +leaves him. Anon comes in a fellow, takes off his +crown, kisses it, and pours poison in the King's +ears, and exit. The Queen returns; finds the King +dead, and makes passionate action. The Poisoner, +with some two or three Mutes, comes in again, +seeming to lament with her. The dead body is +carried away. The Poisoner wooes the Queen with +gifts: she seems loath and unwilling awhile, but +in the end accepts his love +Exeunt + + +OPHELIA +What means this, my lord? + + + +HAMLET +Marry, this is miching mallecho; it means mischief. + + + +OPHELIA +Belike this show imports the argument of the play. + + + +Enter Prologue + + +HAMLET +We shall know by this fellow: the players cannot +keep counsel; they'll tell all. + + + +OPHELIA +Will he tell us what this show meant? + + + +HAMLET +Ay, or any show that you'll show him: be not you +ashamed to show, he'll not shame to tell you what it means. + + + +OPHELIA +You are naught, you are naught: I'll mark the play. + + + +Prologue +For us, and for our tragedy, +Here stooping to your clemency, +We beg your hearing patiently. + + + +Exit + + +HAMLET +Is this a prologue, or the posy of a ring? + + + +OPHELIA +'Tis brief, my lord. + + + +HAMLET +As woman's love. + + + +Enter two Players, King and Queen + + +Player King +Full thirty times hath Phoebus' cart gone round +Neptune's salt wash and Tellus' orbed ground, +And thirty dozen moons with borrow'd sheen +About the world have times twelve thirties been, +Since love our hearts and Hymen did our hands +Unite commutual in most sacred bands. + + + +Player Queen +So many journeys may the sun and moon +Make us again count o'er ere love be done! +But, woe is me, you are so sick of late, +So far from cheer and from your former state, +That I distrust you. Yet, though I distrust, +Discomfort you, my lord, it nothing must: +For women's fear and love holds quantity; +In neither aught, or in extremity. +Now, what my love is, proof hath made you know; +And as my love is sized, my fear is so: +Where love is great, the littlest doubts are fear; +Where little fears grow great, great love grows there. + + + +Player King +'Faith, I must leave thee, love, and shortly too; +My operant powers their functions leave to do: +And thou shalt live in this fair world behind, +Honour'd, beloved; and haply one as kind +For husband shalt thou-- + + + +Player Queen +O, confound the rest! +Such love must needs be treason in my breast: +In second husband let me be accurst! +None wed the second but who kill'd the first. + + + +HAMLET +Aside Wormwood, wormwood. + + + +Player Queen +The instances that second marriage move +Are base respects of thrift, but none of love: +A second time I kill my husband dead, +When second husband kisses me in bed. + + + +Player King +I do believe you think what now you speak; +But what we do determine oft we break. +Purpose is but the slave to memory, +Of violent birth, but poor validity; +Which now, like fruit unripe, sticks on the tree; +But fall, unshaken, when they mellow be. +Most necessary 'tis that we forget +To pay ourselves what to ourselves is debt: +What to ourselves in passion we propose, +The passion ending, doth the purpose lose. +The violence of either grief or joy +Their own enactures with themselves destroy: +Where joy most revels, grief doth most lament; +Grief joys, joy grieves, on slender accident. +This world is not for aye, nor 'tis not strange +That even our loves should with our fortunes change; +For 'tis a question left us yet to prove, +Whether love lead fortune, or else fortune love. +The great man down, you mark his favourite flies; +The poor advanced makes friends of enemies. +And hitherto doth love on fortune tend; +For who not needs shall never lack a friend, +And who in want a hollow friend doth try, +Directly seasons him his enemy. +But, orderly to end where I begun, +Our wills and fates do so contrary run +That our devices still are overthrown; +Our thoughts are ours, their ends none of our own: +So think thou wilt no second husband wed; +But die thy thoughts when thy first lord is dead. + + + +Player Queen +Nor earth to me give food, nor heaven light! +Sport and repose lock from me day and night! +To desperation turn my trust and hope! +An anchor's cheer in prison be my scope! +Each opposite that blanks the face of joy +Meet what I would have well and it destroy! +Both here and hence pursue me lasting strife, +If, once a widow, ever I be wife! + + + +HAMLET +If she should break it now! + + + +Player King +'Tis deeply sworn. Sweet, leave me here awhile; +My spirits grow dull, and fain I would beguile +The tedious day with sleep. + + + +Sleeps + + +Player Queen +Sleep rock thy brain, +And never come mischance between us twain! + + + +Exit + + +HAMLET +Madam, how like you this play? + + + +QUEEN GERTRUDE +The lady protests too much, methinks. + + + +HAMLET +O, but she'll keep her word. + + + +KING CLAUDIUS +Have you heard the argument? Is there no offence in 't? + + + +HAMLET +No, no, they do but jest, poison in jest; no offence +i' the world. + + + +KING CLAUDIUS +What do you call the play? + + + +HAMLET +The Mouse-trap. Marry, how? Tropically. This play +is the image of a murder done in Vienna: Gonzago is +the duke's name; his wife, Baptista: you shall see +anon; 'tis a knavish piece of work: but what o' +that? your majesty and we that have free souls, it +touches us not: let the galled jade wince, our +withers are unwrung. +Enter LUCIANUS +This is one Lucianus, nephew to the king. + + + +OPHELIA +You are as good as a chorus, my lord. + + + +HAMLET +I could interpret between you and your love, if I +could see the puppets dallying. + + + +OPHELIA +You are keen, my lord, you are keen. + + + +HAMLET +It would cost you a groaning to take off my edge. + + + +OPHELIA +Still better, and worse. + + + +HAMLET +So you must take your husbands. Begin, murderer; +pox, leave thy damnable faces, and begin. Come: +'the croaking raven doth bellow for revenge.' + + + +LUCIANUS +Thoughts black, hands apt, drugs fit, and time agreeing; +Confederate season, else no creature seeing; +Thou mixture rank, of midnight weeds collected, +With Hecate's ban thrice blasted, thrice infected, +Thy natural magic and dire property, +On wholesome life usurp immediately. + + + +Pours the poison into the sleeper's ears + + +HAMLET +He poisons him i' the garden for's estate. His +name's Gonzago: the story is extant, and writ in +choice Italian: you shall see anon how the murderer +gets the love of Gonzago's wife. + + + +OPHELIA +The king rises. + + + +HAMLET +What, frighted with false fire! + + + +QUEEN GERTRUDE +How fares my lord? + + + +LORD POLONIUS +Give o'er the play. + + + +KING CLAUDIUS +Give me some light: away! + + + +All +Lights, lights, lights! + + + +Exeunt all but HAMLET and HORATIO + + +HAMLET +Why, let the stricken deer go weep, +The hart ungalled play; +For some must watch, while some must sleep: +So runs the world away. +Would not this, sir, and a forest of feathers-- if +the rest of my fortunes turn Turk with me--with two +Provincial roses on my razed shoes, get me a +fellowship in a cry of players, sir? + + + +HORATIO +Half a share. + + + +HAMLET +A whole one, I. +For thou dost know, O Damon dear, +This realm dismantled was +Of Jove himself; and now reigns here +A very, very--pajock. + + + +HORATIO +You might have rhymed. + + + +HAMLET +O good Horatio, I'll take the ghost's word for a +thousand pound. Didst perceive? + + + +HORATIO +Very well, my lord. + + + +HAMLET +Upon the talk of the poisoning? + + + +HORATIO +I did very well note him. + + + +HAMLET +Ah, ha! Come, some music! come, the recorders! +For if the king like not the comedy, +Why then, belike, he likes it not, perdy. +Come, some music! + + + +Re-enter ROSENCRANTZ and GUILDENSTERN + + +GUILDENSTERN +Good my lord, vouchsafe me a word with you. + + + +HAMLET +Sir, a whole history. + + + +GUILDENSTERN +The king, sir,-- + + + +HAMLET +Ay, sir, what of him? + + + +GUILDENSTERN +Is in his retirement marvellous distempered. + + + +HAMLET +With drink, sir? + + + +GUILDENSTERN +No, my lord, rather with choler. + + + +HAMLET +Your wisdom should show itself more richer to +signify this to his doctor; for, for me to put him +to his purgation would perhaps plunge him into far +more choler. + + + +GUILDENSTERN +Good my lord, put your discourse into some frame and +start not so wildly from my affair. + + + +HAMLET +I am tame, sir: pronounce. + + + +GUILDENSTERN +The queen, your mother, in most great affliction of +spirit, hath sent me to you. + + + +HAMLET +You are welcome. + + + +GUILDENSTERN +Nay, good my lord, this courtesy is not of the right +breed. If it shall please you to make me a +wholesome answer, I will do your mother's +commandment: if not, your pardon and my return +shall be the end of my business. + + + +HAMLET +Sir, I cannot. + + + +GUILDENSTERN +What, my lord? + + + +HAMLET +Make you a wholesome answer; my wit's diseased: but, +sir, such answer as I can make, you shall command; +or, rather, as you say, my mother: therefore no +more, but to the matter: my mother, you say,-- + + + +ROSENCRANTZ +Then thus she says; your behavior hath struck her +into amazement and admiration. + + + +HAMLET +O wonderful son, that can so astonish a mother! But +is there no sequel at the heels of this mother's +admiration? Impart. + + + +ROSENCRANTZ +She desires to speak with you in her closet, ere you +go to bed. + + + +HAMLET +We shall obey, were she ten times our mother. Have +you any further trade with us? + + + +ROSENCRANTZ +My lord, you once did love me. + + + +HAMLET +So I do still, by these pickers and stealers. + + + +ROSENCRANTZ +Good my lord, what is your cause of distemper? you +do, surely, bar the door upon your own liberty, if +you deny your griefs to your friend. + + + +HAMLET +Sir, I lack advancement. + + + +ROSENCRANTZ +How can that be, when you have the voice of the king +himself for your succession in Denmark? + + + +HAMLET +Ay, but sir, 'While the grass grows,'--the proverb +is something musty. +Re-enter Players with recorders +O, the recorders! let me see one. To withdraw with +you:--why do you go about to recover the wind of me, +as if you would drive me into a toil? + + + +GUILDENSTERN +O, my lord, if my duty be too bold, my love is too +unmannerly. + + + +HAMLET +I do not well understand that. Will you play upon +this pipe? + + + +GUILDENSTERN +My lord, I cannot. + + + +HAMLET +I pray you. + + + +GUILDENSTERN +Believe me, I cannot. + + + +HAMLET +I do beseech you. + + + +GUILDENSTERN +I know no touch of it, my lord. + + + +HAMLET +'Tis as easy as lying: govern these ventages with +your lingers and thumb, give it breath with your +mouth, and it will discourse most eloquent music. +Look you, these are the stops. + + + +GUILDENSTERN +But these cannot I command to any utterance of +harmony; I have not the skill. + + + +HAMLET +Why, look you now, how unworthy a thing you make of +me! You would play upon me; you would seem to know +my stops; you would pluck out the heart of my +mystery; you would sound me from my lowest note to +the top of my compass: and there is much music, +excellent voice, in this little organ; yet cannot +you make it speak. 'Sblood, do you think I am +easier to be played on than a pipe? Call me what +instrument you will, though you can fret me, yet you +cannot play upon me. +Enter POLONIUS +God bless you, sir! + + + +LORD POLONIUS +My lord, the queen would speak with you, and +presently. + + + +HAMLET +Do you see yonder cloud that's almost in shape of a camel? + + + +LORD POLONIUS +By the mass, and 'tis like a camel, indeed. + + + +HAMLET +Methinks it is like a weasel. + + + +LORD POLONIUS +It is backed like a weasel. + + + +HAMLET +Or like a whale? + + + +LORD POLONIUS +Very like a whale. + + + +HAMLET +Then I will come to my mother by and by. They fool +me to the top of my bent. I will come by and by. + + + +LORD POLONIUS +I will say so. + + + +HAMLET +By and by is easily said. +Exit POLONIUS +Leave me, friends. +Exeunt all but HAMLET +Tis now the very witching time of night, +When churchyards yawn and hell itself breathes out +Contagion to this world: now could I drink hot blood, +And do such bitter business as the day +Would quake to look on. Soft! now to my mother. +O heart, lose not thy nature; let not ever +The soul of Nero enter this firm bosom: +Let me be cruel, not unnatural: +I will speak daggers to her, but use none; +My tongue and soul in this be hypocrites; +How in my words soever she be shent, +To give them seals never, my soul, consent! + + + +Exit + + +SCENE III. A room in the castle. +Enter KING CLAUDIUS, ROSENCRANTZ, and GUILDENSTERN + + +KING CLAUDIUS +I like him not, nor stands it safe with us +To let his madness range. Therefore prepare you; +I your commission will forthwith dispatch, +And he to England shall along with you: +The terms of our estate may not endure +Hazard so dangerous as doth hourly grow +Out of his lunacies. + + + +GUILDENSTERN +We will ourselves provide: +Most holy and religious fear it is +To keep those many many bodies safe +That live and feed upon your majesty. + + + +ROSENCRANTZ +The single and peculiar life is bound, +With all the strength and armour of the mind, +To keep itself from noyance; but much more +That spirit upon whose weal depend and rest +The lives of many. The cease of majesty +Dies not alone; but, like a gulf, doth draw +What's near it with it: it is a massy wheel, +Fix'd on the summit of the highest mount, +To whose huge spokes ten thousand lesser things +Are mortised and adjoin'd; which, when it falls, +Each small annexment, petty consequence, +Attends the boisterous ruin. Never alone +Did the king sigh, but with a general groan. + + + +KING CLAUDIUS +Arm you, I pray you, to this speedy voyage; +For we will fetters put upon this fear, +Which now goes too free-footed. + + + +ROSENCRANTZ +GUILDENSTERN +We will haste us. + + +Exeunt ROSENCRANTZ and GUILDENSTERN +Enter POLONIUS + + +LORD POLONIUS +My lord, he's going to his mother's closet: +Behind the arras I'll convey myself, +To hear the process; and warrant she'll tax him home: +And, as you said, and wisely was it said, +'Tis meet that some more audience than a mother, +Since nature makes them partial, should o'erhear +The speech, of vantage. Fare you well, my liege: +I'll call upon you ere you go to bed, +And tell you what I know. + + + +KING CLAUDIUS +Thanks, dear my lord. +Exit POLONIUS +O, my offence is rank it smells to heaven; +It hath the primal eldest curse upon't, +A brother's murder. Pray can I not, +Though inclination be as sharp as will: +My stronger guilt defeats my strong intent; +And, like a man to double business bound, +I stand in pause where I shall first begin, +And both neglect. What if this cursed hand +Were thicker than itself with brother's blood, +Is there not rain enough in the sweet heavens +To wash it white as snow? Whereto serves mercy +But to confront the visage of offence? +And what's in prayer but this two-fold force, +To be forestalled ere we come to fall, +Or pardon'd being down? Then I'll look up; +My fault is past. But, O, what form of prayer +Can serve my turn? 'Forgive me my foul murder'? +That cannot be; since I am still possess'd +Of those effects for which I did the murder, +My crown, mine own ambition and my queen. +May one be pardon'd and retain the offence? +In the corrupted currents of this world +Offence's gilded hand may shove by justice, +And oft 'tis seen the wicked prize itself +Buys out the law: but 'tis not so above; +There is no shuffling, there the action lies +In his true nature; and we ourselves compell'd, +Even to the teeth and forehead of our faults, +To give in evidence. What then? what rests? +Try what repentance can: what can it not? +Yet what can it when one can not repent? +O wretched state! O bosom black as death! +O limed soul, that, struggling to be free, +Art more engaged! Help, angels! Make assay! +Bow, stubborn knees; and, heart with strings of steel, +Be soft as sinews of the newborn babe! +All may be well. + + +Retires and kneels +Enter HAMLET + + +HAMLET +Now might I do it pat, now he is praying; +And now I'll do't. And so he goes to heaven; +And so am I revenged. That would be scann'd: +A villain kills my father; and for that, +I, his sole son, do this same villain send +To heaven. +O, this is hire and salary, not revenge. +He took my father grossly, full of bread; +With all his crimes broad blown, as flush as May; +And how his audit stands who knows save heaven? +But in our circumstance and course of thought, +'Tis heavy with him: and am I then revenged, +To take him in the purging of his soul, +When he is fit and season'd for his passage? +No! +Up, sword; and know thou a more horrid hent: +When he is drunk asleep, or in his rage, +Or in the incestuous pleasure of his bed; +At gaming, swearing, or about some act +That has no relish of salvation in't; +Then trip him, that his heels may kick at heaven, +And that his soul may be as damn'd and black +As hell, whereto it goes. My mother stays: +This physic but prolongs thy sickly days. + + + +Exit + + +KING CLAUDIUS +Rising My words fly up, my thoughts remain below: +Words without thoughts never to heaven go. + + + +Exit + + +SCENE IV. The Queen's closet. +Enter QUEEN MARGARET and POLONIUS + + +LORD POLONIUS +He will come straight. Look you lay home to him: +Tell him his pranks have been too broad to bear with, +And that your grace hath screen'd and stood between +Much heat and him. I'll sconce me even here. +Pray you, be round with him. + + + +HAMLET +Within Mother, mother, mother! + + + +QUEEN GERTRUDE +I'll warrant you, +Fear me not: withdraw, I hear him coming. + + +POLONIUS hides behind the arras +Enter HAMLET + + +HAMLET +Now, mother, what's the matter? + + + +QUEEN GERTRUDE +Hamlet, thou hast thy father much offended. + + + +HAMLET +Mother, you have my father much offended. + + + +QUEEN GERTRUDE +Come, come, you answer with an idle tongue. + + + +HAMLET +Go, go, you question with a wicked tongue. + + + +QUEEN GERTRUDE +Why, how now, Hamlet! + + + +HAMLET +What's the matter now? + + + +QUEEN GERTRUDE +Have you forgot me? + + + +HAMLET +No, by the rood, not so: +You are the queen, your husband's brother's wife; +And--would it were not so!--you are my mother. + + + +QUEEN GERTRUDE +Nay, then, I'll set those to you that can speak. + + + +HAMLET +Come, come, and sit you down; you shall not budge; +You go not till I set you up a glass +Where you may see the inmost part of you. + + + +QUEEN GERTRUDE +What wilt thou do? thou wilt not murder me? +Help, help, ho! + + + +LORD POLONIUS +Behind What, ho! help, help, help! + + + +HAMLET +Drawing How now! a rat? Dead, for a ducat, dead! + + + +Makes a pass through the arras + + +LORD POLONIUS +Behind O, I am slain! + + + +Falls and dies + + +QUEEN GERTRUDE +O me, what hast thou done? + + + +HAMLET +Nay, I know not: +Is it the king? + + + +QUEEN GERTRUDE +O, what a rash and bloody deed is this! + + + +HAMLET +A bloody deed! almost as bad, good mother, +As kill a king, and marry with his brother. + + + +QUEEN GERTRUDE +As kill a king! + + + +HAMLET +Ay, lady, 'twas my word. +Lifts up the array and discovers POLONIUS +Thou wretched, rash, intruding fool, farewell! +I took thee for thy better: take thy fortune; +Thou find'st to be too busy is some danger. +Leave wringing of your hands: peace! sit you down, +And let me wring your heart; for so I shall, +If it be made of penetrable stuff, +If damned custom have not brass'd it so +That it is proof and bulwark against sense. + + + +QUEEN GERTRUDE +What have I done, that thou darest wag thy tongue +In noise so rude against me? + + + +HAMLET +Such an act +That blurs the grace and blush of modesty, +Calls virtue hypocrite, takes off the rose +From the fair forehead of an innocent love +And sets a blister there, makes marriage-vows +As false as dicers' oaths: O, such a deed +As from the body of contraction plucks +The very soul, and sweet religion makes +A rhapsody of words: heaven's face doth glow: +Yea, this solidity and compound mass, +With tristful visage, as against the doom, +Is thought-sick at the act. + + + +QUEEN GERTRUDE +Ay me, what act, +That roars so loud, and thunders in the index? + + + +HAMLET +Look here, upon this picture, and on this, +The counterfeit presentment of two brothers. +See, what a grace was seated on this brow; +Hyperion's curls; the front of Jove himself; +An eye like Mars, to threaten and command; +A station like the herald Mercury +New-lighted on a heaven-kissing hill; +A combination and a form indeed, +Where every god did seem to set his seal, +To give the world assurance of a man: +This was your husband. Look you now, what follows: +Here is your husband; like a mildew'd ear, +Blasting his wholesome brother. Have you eyes? +Could you on this fair mountain leave to feed, +And batten on this moor? Ha! have you eyes? +You cannot call it love; for at your age +The hey-day in the blood is tame, it's humble, +And waits upon the judgment: and what judgment +Would step from this to this? Sense, sure, you have, +Else could you not have motion; but sure, that sense +Is apoplex'd; for madness would not err, +Nor sense to ecstasy was ne'er so thrall'd +But it reserved some quantity of choice, +To serve in such a difference. What devil was't +That thus hath cozen'd you at hoodman-blind? +Eyes without feeling, feeling without sight, +Ears without hands or eyes, smelling sans all, +Or but a sickly part of one true sense +Could not so mope. +O shame! where is thy blush? Rebellious hell, +If thou canst mutine in a matron's bones, +To flaming youth let virtue be as wax, +And melt in her own fire: proclaim no shame +When the compulsive ardour gives the charge, +Since frost itself as actively doth burn +And reason panders will. + + + +QUEEN GERTRUDE +O Hamlet, speak no more: +Thou turn'st mine eyes into my very soul; +And there I see such black and grained spots +As will not leave their tinct. + + + +HAMLET +Nay, but to live +In the rank sweat of an enseamed bed, +Stew'd in corruption, honeying and making love +Over the nasty sty,-- + + + +QUEEN GERTRUDE +O, speak to me no more; +These words, like daggers, enter in mine ears; +No more, sweet Hamlet! + + + +HAMLET +A murderer and a villain; +A slave that is not twentieth part the tithe +Of your precedent lord; a vice of kings; +A cutpurse of the empire and the rule, +That from a shelf the precious diadem stole, +And put it in his pocket! + + + +QUEEN GERTRUDE +No more! + + + +HAMLET +A king of shreds and patches,-- +Enter Ghost +Save me, and hover o'er me with your wings, +You heavenly guards! What would your gracious figure? + + + +QUEEN GERTRUDE +Alas, he's mad! + + + +HAMLET +Do you not come your tardy son to chide, +That, lapsed in time and passion, lets go by +The important acting of your dread command? O, say! + + + +Ghost +Do not forget: this visitation +Is but to whet thy almost blunted purpose. +But, look, amazement on thy mother sits: +O, step between her and her fighting soul: +Conceit in weakest bodies strongest works: +Speak to her, Hamlet. + + + +HAMLET +How is it with you, lady? + + + +QUEEN GERTRUDE +Alas, how is't with you, +That you do bend your eye on vacancy +And with the incorporal air do hold discourse? +Forth at your eyes your spirits wildly peep; +And, as the sleeping soldiers in the alarm, +Your bedded hair, like life in excrements, +Starts up, and stands on end. O gentle son, +Upon the heat and flame of thy distemper +Sprinkle cool patience. Whereon do you look? + + + +HAMLET +On him, on him! Look you, how pale he glares! +His form and cause conjoin'd, preaching to stones, +Would make them capable. Do not look upon me; +Lest with this piteous action you convert +My stern effects: then what I have to do +Will want true colour; tears perchance for blood. + + + +QUEEN GERTRUDE +To whom do you speak this? + + + +HAMLET +Do you see nothing there? + + + +QUEEN GERTRUDE +Nothing at all; yet all that is I see. + + + +HAMLET +Nor did you nothing hear? + + + +QUEEN GERTRUDE +No, nothing but ourselves. + + + +HAMLET +Why, look you there! look, how it steals away! +My father, in his habit as he lived! +Look, where he goes, even now, out at the portal! + + + +Exit Ghost + + +QUEEN GERTRUDE +This the very coinage of your brain: +This bodiless creation ecstasy +Is very cunning in. + + + +HAMLET +Ecstasy! +My pulse, as yours, doth temperately keep time, +And makes as healthful music: it is not madness +That I have utter'd: bring me to the test, +And I the matter will re-word; which madness +Would gambol from. Mother, for love of grace, +Lay not that mattering unction to your soul, +That not your trespass, but my madness speaks: +It will but skin and film the ulcerous place, +Whilst rank corruption, mining all within, +Infects unseen. Confess yourself to heaven; +Repent what's past; avoid what is to come; +And do not spread the compost on the weeds, +To make them ranker. Forgive me this my virtue; +For in the fatness of these pursy times +Virtue itself of vice must pardon beg, +Yea, curb and woo for leave to do him good. + + + +QUEEN GERTRUDE +O Hamlet, thou hast cleft my heart in twain. + + + +HAMLET +O, throw away the worser part of it, +And live the purer with the other half. +Good night: but go not to mine uncle's bed; +Assume a virtue, if you have it not. +That monster, custom, who all sense doth eat, +Of habits devil, is angel yet in this, +That to the use of actions fair and good +He likewise gives a frock or livery, +That aptly is put on. Refrain to-night, +And that shall lend a kind of easiness +To the next abstinence: the next more easy; +For use almost can change the stamp of nature, +And either ... the devil, or throw him out +With wondrous potency. Once more, good night: +And when you are desirous to be bless'd, +I'll blessing beg of you. For this same lord, +Pointing to POLONIUS +I do repent: but heaven hath pleased it so, +To punish me with this and this with me, +That I must be their scourge and minister. +I will bestow him, and will answer well +The death I gave him. So, again, good night. +I must be cruel, only to be kind: +Thus bad begins and worse remains behind. +One word more, good lady. + + + +QUEEN GERTRUDE +What shall I do? + + + +HAMLET +Not this, by no means, that I bid you do: +Let the bloat king tempt you again to bed; +Pinch wanton on your cheek; call you his mouse; +And let him, for a pair of reechy kisses, +Or paddling in your neck with his damn'd fingers, +Make you to ravel all this matter out, +That I essentially am not in madness, +But mad in craft. 'Twere good you let him know; +For who, that's but a queen, fair, sober, wise, +Would from a paddock, from a bat, a gib, +Such dear concernings hide? who would do so? +No, in despite of sense and secrecy, +Unpeg the basket on the house's top. +Let the birds fly, and, like the famous ape, +To try conclusions, in the basket creep, +And break your own neck down. + + + +QUEEN GERTRUDE +Be thou assured, if words be made of breath, +And breath of life, I have no life to breathe +What thou hast said to me. + + + +HAMLET +I must to England; you know that? + + + +QUEEN GERTRUDE +Alack, +I had forgot: 'tis so concluded on. + + + +HAMLET +There's letters seal'd: and my two schoolfellows, +Whom I will trust as I will adders fang'd, +They bear the mandate; they must sweep my way, +And marshal me to knavery. Let it work; +For 'tis the sport to have the engineer +Hoist with his own petard: and 't shall go hard +But I will delve one yard below their mines, +And blow them at the moon: O, 'tis most sweet, +When in one line two crafts directly meet. +This man shall set me packing: +I'll lug the guts into the neighbour room. +Mother, good night. Indeed this counsellor +Is now most still, most secret and most grave, +Who was in life a foolish prating knave. +Come, sir, to draw toward an end with you. +Good night, mother. + + + +Exeunt severally; HAMLET dragging in POLONIUS + + + + +ACT IV + +SCENE I. A room in the castle. +Enter KING CLAUDIUS, QUEEN GERTRUDE, ROSENCRANTZ, +and GUILDENSTERN + + +KING CLAUDIUS +There's matter in these sighs, these profound heaves: +You must translate: 'tis fit we understand them. +Where is your son? + + + +QUEEN GERTRUDE +Bestow this place on us a little while. +Exeunt ROSENCRANTZ and GUILDENSTERN +Ah, my good lord, what have I seen to-night! + + + +KING CLAUDIUS +What, Gertrude? How does Hamlet? + + + +QUEEN GERTRUDE +Mad as the sea and wind, when both contend +Which is the mightier: in his lawless fit, +Behind the arras hearing something stir, +Whips out his rapier, cries, 'A rat, a rat!' +And, in this brainish apprehension, kills +The unseen good old man. + + + +KING CLAUDIUS +O heavy deed! +It had been so with us, had we been there: +His liberty is full of threats to all; +To you yourself, to us, to every one. +Alas, how shall this bloody deed be answer'd? +It will be laid to us, whose providence +Should have kept short, restrain'd and out of haunt, +This mad young man: but so much was our love, +We would not understand what was most fit; +But, like the owner of a foul disease, +To keep it from divulging, let it feed +Even on the pith of Life. Where is he gone? + + + +QUEEN GERTRUDE +To draw apart the body he hath kill'd: +O'er whom his very madness, like some ore +Among a mineral of metals base, +Shows itself pure; he weeps for what is done. + + + +KING CLAUDIUS +O Gertrude, come away! +The sun no sooner shall the mountains touch, +But we will ship him hence: and this vile deed +We must, with all our majesty and skill, +Both countenance and excuse. Ho, Guildenstern! +Re-enter ROSENCRANTZ and GUILDENSTERN +Friends both, go join you with some further aid: +Hamlet in madness hath Polonius slain, +And from his mother's closet hath he dragg'd him: +Go seek him out; speak fair, and bring the body +Into the chapel. I pray you, haste in this. +Exeunt ROSENCRANTZ and GUILDENSTERN +Come, Gertrude, we'll call up our wisest friends; +And let them know, both what we mean to do, +And what's untimely done... +Whose whisper o'er the world's diameter, +As level as the cannon to his blank, +Transports his poison'd shot, may miss our name, +And hit the woundless air. O, come away! +My soul is full of discord and dismay. + + + +Exeunt + + +SCENE II. Another room in the castle. +Enter HAMLET + + +HAMLET +Safely stowed. + + + +ROSENCRANTZ +GUILDENSTERN +Within Hamlet! Lord Hamlet! + + + +HAMLET +What noise? who calls on Hamlet? +O, here they come. + + + +Enter ROSENCRANTZ and GUILDENSTERN + + +ROSENCRANTZ +What have you done, my lord, with the dead body? + + + +HAMLET +Compounded it with dust, whereto 'tis kin. + + + +ROSENCRANTZ +Tell us where 'tis, that we may take it thence +And bear it to the chapel. + + + +HAMLET +Do not believe it. + + + +ROSENCRANTZ +Believe what? + + + +HAMLET +That I can keep your counsel and not mine own. +Besides, to be demanded of a sponge! what +replication should be made by the son of a king? + + + +ROSENCRANTZ +Take you me for a sponge, my lord? + + + +HAMLET +Ay, sir, that soaks up the king's countenance, his +rewards, his authorities. But such officers do the +king best service in the end: he keeps them, like +an ape, in the corner of his jaw; first mouthed, to +be last swallowed: when he needs what you have +gleaned, it is but squeezing you, and, sponge, you +shall be dry again. + + + +ROSENCRANTZ +I understand you not, my lord. + + + +HAMLET +I am glad of it: a knavish speech sleeps in a +foolish ear. + + + +ROSENCRANTZ +My lord, you must tell us where the body is, and go +with us to the king. + + + +HAMLET +The body is with the king, but the king is not with +the body. The king is a thing-- + + + +GUILDENSTERN +A thing, my lord! + + + +HAMLET +Of nothing: bring me to him. Hide fox, and all after. + + + +Exeunt + + +SCENE III. Another room in the castle. +Enter KING CLAUDIUS, attended + + +KING CLAUDIUS +I have sent to seek him, and to find the body. +How dangerous is it that this man goes loose! +Yet must not we put the strong law on him: +He's loved of the distracted multitude, +Who like not in their judgment, but their eyes; +And where tis so, the offender's scourge is weigh'd, +But never the offence. To bear all smooth and even, +This sudden sending him away must seem +Deliberate pause: diseases desperate grown +By desperate appliance are relieved, +Or not at all. +Enter ROSENCRANTZ +How now! what hath befall'n? + + + +ROSENCRANTZ +Where the dead body is bestow'd, my lord, +We cannot get from him. + + + +KING CLAUDIUS +But where is he? + + + +ROSENCRANTZ +Without, my lord; guarded, to know your pleasure. + + + +KING CLAUDIUS +Bring him before us. + + + +ROSENCRANTZ +Ho, Guildenstern! bring in my lord. + + + +Enter HAMLET and GUILDENSTERN + + +KING CLAUDIUS +Now, Hamlet, where's Polonius? + + + +HAMLET +At supper. + + + +KING CLAUDIUS +At supper! where? + + + +HAMLET +Not where he eats, but where he is eaten: a certain +convocation of politic worms are e'en at him. Your +worm is your only emperor for diet: we fat all +creatures else to fat us, and we fat ourselves for +maggots: your fat king and your lean beggar is but +variable service, two dishes, but to one table: +that's the end. + + + +KING CLAUDIUS +Alas, alas! + + + +HAMLET +A man may fish with the worm that hath eat of a +king, and cat of the fish that hath fed of that worm. + + + +KING CLAUDIUS +What dost you mean by this? + + + +HAMLET +Nothing but to show you how a king may go a +progress through the guts of a beggar. + + + +KING CLAUDIUS +Where is Polonius? + + + +HAMLET +In heaven; send hither to see: if your messenger +find him not there, seek him i' the other place +yourself. But indeed, if you find him not within +this month, you shall nose him as you go up the +stairs into the lobby. + + + +KING CLAUDIUS +Go seek him there. + + + +To some Attendants + + +HAMLET +He will stay till ye come. + + + +Exeunt Attendants + + +KING CLAUDIUS +Hamlet, this deed, for thine especial safety,-- +Which we do tender, as we dearly grieve +For that which thou hast done,--must send thee hence +With fiery quickness: therefore prepare thyself; +The bark is ready, and the wind at help, +The associates tend, and every thing is bent +For England. + + + +HAMLET +For England! + + + +KING CLAUDIUS +Ay, Hamlet. + + + +HAMLET +Good. + + + +KING CLAUDIUS +So is it, if thou knew'st our purposes. + + + +HAMLET +I see a cherub that sees them. But, come; for +England! Farewell, dear mother. + + + +KING CLAUDIUS +Thy loving father, Hamlet. + + + +HAMLET +My mother: father and mother is man and wife; man +and wife is one flesh; and so, my mother. Come, for England! + + + +Exit + + +KING CLAUDIUS +Follow him at foot; tempt him with speed aboard; +Delay it not; I'll have him hence to-night: +Away! for every thing is seal'd and done +That else leans on the affair: pray you, make haste. +Exeunt ROSENCRANTZ and GUILDENSTERN +And, England, if my love thou hold'st at aught-- +As my great power thereof may give thee sense, +Since yet thy cicatrice looks raw and red +After the Danish sword, and thy free awe +Pays homage to us--thou mayst not coldly set +Our sovereign process; which imports at full, +By letters congruing to that effect, +The present death of Hamlet. Do it, England; +For like the hectic in my blood he rages, +And thou must cure me: till I know 'tis done, +Howe'er my haps, my joys were ne'er begun. + + + +Exit + + +SCENE IV. A plain in Denmark. +Enter FORTINBRAS, a Captain, and Soldiers, marching + + +PRINCE FORTINBRAS +Go, captain, from me greet the Danish king; +Tell him that, by his licence, Fortinbras +Craves the conveyance of a promised march +Over his kingdom. You know the rendezvous. +If that his majesty would aught with us, +We shall express our duty in his eye; +And let him know so. + + + +Captain +I will do't, my lord. + + + +PRINCE FORTINBRAS +Go softly on. + + +Exeunt FORTINBRAS and Soldiers +Enter HAMLET, ROSENCRANTZ, GUILDENSTERN, and others + + +HAMLET +Good sir, whose powers are these? + + + +Captain +They are of Norway, sir. + + + +HAMLET +How purposed, sir, I pray you? + + + +Captain +Against some part of Poland. + + + +HAMLET +Who commands them, sir? + + + +Captain +The nephews to old Norway, Fortinbras. + + + +HAMLET +Goes it against the main of Poland, sir, +Or for some frontier? + + + +Captain +Truly to speak, and with no addition, +We go to gain a little patch of ground +That hath in it no profit but the name. +To pay five ducats, five, I would not farm it; +Nor will it yield to Norway or the Pole +A ranker rate, should it be sold in fee. + + + +HAMLET +Why, then the Polack never will defend it. + + + +Captain +Yes, it is already garrison'd. + + + +HAMLET +Two thousand souls and twenty thousand ducats +Will not debate the question of this straw: +This is the imposthume of much wealth and peace, +That inward breaks, and shows no cause without +Why the man dies. I humbly thank you, sir. + + + +Captain +God be wi' you, sir. + + + +Exit + + +ROSENCRANTZ +Wilt please you go, my lord? + + + +HAMLET +I'll be with you straight go a little before. +Exeunt all except HAMLET +How all occasions do inform against me, +And spur my dull revenge! What is a man, +If his chief good and market of his time +Be but to sleep and feed? a beast, no more. +Sure, he that made us with such large discourse, +Looking before and after, gave us not +That capability and god-like reason +To fust in us unused. Now, whether it be +Bestial oblivion, or some craven scruple +Of thinking too precisely on the event, +A thought which, quarter'd, hath but one part wisdom +And ever three parts coward, I do not know +Why yet I live to say 'This thing's to do;' +Sith I have cause and will and strength and means +To do't. Examples gross as earth exhort me: +Witness this army of such mass and charge +Led by a delicate and tender prince, +Whose spirit with divine ambition puff'd +Makes mouths at the invisible event, +Exposing what is mortal and unsure +To all that fortune, death and danger dare, +Even for an egg-shell. Rightly to be great +Is not to stir without great argument, +But greatly to find quarrel in a straw +When honour's at the stake. How stand I then, +That have a father kill'd, a mother stain'd, +Excitements of my reason and my blood, +And let all sleep? while, to my shame, I see +The imminent death of twenty thousand men, +That, for a fantasy and trick of fame, +Go to their graves like beds, fight for a plot +Whereon the numbers cannot try the cause, +Which is not tomb enough and continent +To hide the slain? O, from this time forth, +My thoughts be bloody, or be nothing worth! + + + +Exit + + +SCENE V. Elsinore. A room in the castle. +Enter QUEEN GERTRUDE, HORATIO, and a Gentleman + + +QUEEN GERTRUDE +I will not speak with her. + + + +Gentleman +She is importunate, indeed distract: +Her mood will needs be pitied. + + + +QUEEN GERTRUDE +What would she have? + + + +Gentleman +She speaks much of her father; says she hears +There's tricks i' the world; and hems, and beats her heart; +Spurns enviously at straws; speaks things in doubt, +That carry but half sense: her speech is nothing, +Yet the unshaped use of it doth move +The hearers to collection; they aim at it, +And botch the words up fit to their own thoughts; +Which, as her winks, and nods, and gestures +yield them, +Indeed would make one think there might be thought, +Though nothing sure, yet much unhappily. + + + +HORATIO +'Twere good she were spoken with; for she may strew +Dangerous conjectures in ill-breeding minds. + + + +QUEEN GERTRUDE +Let her come in. +Exit HORATIO +To my sick soul, as sin's true nature is, +Each toy seems prologue to some great amiss: +So full of artless jealousy is guilt, +It spills itself in fearing to be spilt. + + + +Re-enter HORATIO, with OPHELIA + + +OPHELIA +Where is the beauteous majesty of Denmark? + + + +QUEEN GERTRUDE +How now, Ophelia! + + + +OPHELIA +Sings +How should I your true love know +From another one? +By his cockle hat and staff, +And his sandal shoon. + + + +QUEEN GERTRUDE +Alas, sweet lady, what imports this song? + + + +OPHELIA +Say you? nay, pray you, mark. +Sings +He is dead and gone, lady, +He is dead and gone; +At his head a grass-green turf, +At his heels a stone. + + + +QUEEN GERTRUDE +Nay, but, Ophelia,-- + + + +OPHELIA +Pray you, mark. +Sings +White his shroud as the mountain snow,-- + + + +Enter KING CLAUDIUS + + +QUEEN GERTRUDE +Alas, look here, my lord. + + + +OPHELIA +Sings +Larded with sweet flowers +Which bewept to the grave did go +With true-love showers. + + + +KING CLAUDIUS +How do you, pretty lady? + + + +OPHELIA +Well, God 'ild you! They say the owl was a baker's +daughter. Lord, we know what we are, but know not +what we may be. God be at your table! + + + +KING CLAUDIUS +Conceit upon her father. + + + +OPHELIA +Pray you, let's have no words of this; but when they +ask you what it means, say you this: +Sings +To-morrow is Saint Valentine's day, +All in the morning betime, +And I a maid at your window, +To be your Valentine. +Then up he rose, and donn'd his clothes, +And dupp'd the chamber-door; +Let in the maid, that out a maid +Never departed more. + + + +KING CLAUDIUS +Pretty Ophelia! + + + +OPHELIA +Indeed, la, without an oath, I'll make an end on't: +Sings +By Gis and by Saint Charity, +Alack, and fie for shame! +Young men will do't, if they come to't; +By cock, they are to blame. +Quoth she, before you tumbled me, +You promised me to wed. +So would I ha' done, by yonder sun, +An thou hadst not come to my bed. + + + +KING CLAUDIUS +How long hath she been thus? + + + +OPHELIA +I hope all will be well. We must be patient: but I +cannot choose but weep, to think they should lay him +i' the cold ground. My brother shall know of it: +and so I thank you for your good counsel. Come, my +coach! Good night, ladies; good night, sweet ladies; +good night, good night. + + + +Exit + + +KING CLAUDIUS +Follow her close; give her good watch, +I pray you. +Exit HORATIO +O, this is the poison of deep grief; it springs +All from her father's death. O Gertrude, Gertrude, +When sorrows come, they come not single spies +But in battalions. First, her father slain: +Next, your son gone; and he most violent author +Of his own just remove: the people muddied, +Thick and unwholesome in their thoughts and whispers, +For good Polonius' death; and we have done but greenly, +In hugger-mugger to inter him: poor Ophelia +Divided from herself and her fair judgment, +Without the which we are pictures, or mere beasts: +Last, and as much containing as all these, +Her brother is in secret come from France; +Feeds on his wonder, keeps himself in clouds, +And wants not buzzers to infect his ear +With pestilent speeches of his father's death; +Wherein necessity, of matter beggar'd, +Will nothing stick our person to arraign +In ear and ear. O my dear Gertrude, this, +Like to a murdering-piece, in many places +Gives me superfluous death. + + + +A noise within + + +QUEEN GERTRUDE +Alack, what noise is this? + + + +KING CLAUDIUS +Where are my Switzers? Let them guard the door. +Enter another Gentleman +What is the matter? + + + +Gentleman +Save yourself, my lord: +The ocean, overpeering of his list, +Eats not the flats with more impetuous haste +Than young Laertes, in a riotous head, +O'erbears your officers. The rabble call him lord; +And, as the world were now but to begin, +Antiquity forgot, custom not known, +The ratifiers and props of every word, +They cry 'Choose we: Laertes shall be king:' +Caps, hands, and tongues, applaud it to the clouds: +'Laertes shall be king, Laertes king!' + + + +QUEEN GERTRUDE +How cheerfully on the false trail they cry! +O, this is counter, you false Danish dogs! + + + +KING CLAUDIUS +The doors are broke. + + +Noise within +Enter LAERTES, armed; Danes following + + +LAERTES +Where is this king? Sirs, stand you all without. + + + +Danes +No, let's come in. + + + +LAERTES +I pray you, give me leave. + + + +Danes +We will, we will. + + + +They retire without the door + + +LAERTES +I thank you: keep the door. O thou vile king, +Give me my father! + + + +QUEEN GERTRUDE +Calmly, good Laertes. + + + +LAERTES +That drop of blood that's calm proclaims me bastard, +Cries cuckold to my father, brands the harlot +Even here, between the chaste unsmirched brow +Of my true mother. + + + +KING CLAUDIUS +What is the cause, Laertes, +That thy rebellion looks so giant-like? +Let him go, Gertrude; do not fear our person: +There's such divinity doth hedge a king, +That treason can but peep to what it would, +Acts little of his will. Tell me, Laertes, +Why thou art thus incensed. Let him go, Gertrude. +Speak, man. + + + +LAERTES +Where is my father? + + + +KING CLAUDIUS +Dead. + + + +QUEEN GERTRUDE +But not by him. + + + +KING CLAUDIUS +Let him demand his fill. + + + +LAERTES +How came he dead? I'll not be juggled with: +To hell, allegiance! vows, to the blackest devil! +Conscience and grace, to the profoundest pit! +I dare damnation. To this point I stand, +That both the worlds I give to negligence, +Let come what comes; only I'll be revenged +Most thoroughly for my father. + + + +KING CLAUDIUS +Who shall stay you? + + + +LAERTES +My will, not all the world: +And for my means, I'll husband them so well, +They shall go far with little. + + + +KING CLAUDIUS +Good Laertes, +If you desire to know the certainty +Of your dear father's death, is't writ in your revenge, +That, swoopstake, you will draw both friend and foe, +Winner and loser? + + + +LAERTES +None but his enemies. + + + +KING CLAUDIUS +Will you know them then? + + + +LAERTES +To his good friends thus wide I'll ope my arms; +And like the kind life-rendering pelican, +Repast them with my blood. + + + +KING CLAUDIUS +Why, now you speak +Like a good child and a true gentleman. +That I am guiltless of your father's death, +And am most sensible in grief for it, +It shall as level to your judgment pierce +As day does to your eye. + + + +Danes +Within Let her come in. + + + +LAERTES +How now! what noise is that? +Re-enter OPHELIA +O heat, dry up my brains! tears seven times salt, +Burn out the sense and virtue of mine eye! +By heaven, thy madness shall be paid by weight, +Till our scale turn the beam. O rose of May! +Dear maid, kind sister, sweet Ophelia! +O heavens! is't possible, a young maid's wits +Should be as moral as an old man's life? +Nature is fine in love, and where 'tis fine, +It sends some precious instance of itself +After the thing it loves. + + + +OPHELIA +Sings +They bore him barefaced on the bier; +Hey non nonny, nonny, hey nonny; +And in his grave rain'd many a tear:-- +Fare you well, my dove! + + + +LAERTES +Hadst thou thy wits, and didst persuade revenge, +It could not move thus. + + + +OPHELIA +Sings +You must sing a-down a-down, +An you call him a-down-a. +O, how the wheel becomes it! It is the false +steward, that stole his master's daughter. + + + +LAERTES +This nothing's more than matter. + + + +OPHELIA +There's rosemary, that's for remembrance; pray, +love, remember: and there is pansies. that's for thoughts. + + + +LAERTES +A document in madness, thoughts and remembrance fitted. + + + +OPHELIA +There's fennel for you, and columbines: there's rue +for you; and here's some for me: we may call it +herb-grace o' Sundays: O you must wear your rue with +a difference. There's a daisy: I would give you +some violets, but they withered all when my father +died: they say he made a good end,-- +Sings +For bonny sweet Robin is all my joy. + + + +LAERTES +Thought and affliction, passion, hell itself, +She turns to favour and to prettiness. + + + +OPHELIA +Sings +And will he not come again? +And will he not come again? +No, no, he is dead: +Go to thy death-bed: +He never will come again. +His beard was as white as snow, +All flaxen was his poll: +He is gone, he is gone, +And we cast away moan: +God ha' mercy on his soul! +And of all Christian souls, I pray God. God be wi' ye. + + + +Exit + + +LAERTES +Do you see this, O God? + + + +KING CLAUDIUS +Laertes, I must commune with your grief, +Or you deny me right. Go but apart, +Make choice of whom your wisest friends you will. +And they shall hear and judge 'twixt you and me: +If by direct or by collateral hand +They find us touch'd, we will our kingdom give, +Our crown, our life, and all that we can ours, +To you in satisfaction; but if not, +Be you content to lend your patience to us, +And we shall jointly labour with your soul +To give it due content. + + + +LAERTES +Let this be so; +His means of death, his obscure funeral-- +No trophy, sword, nor hatchment o'er his bones, +No noble rite nor formal ostentation-- +Cry to be heard, as 'twere from heaven to earth, +That I must call't in question. + + + +KING CLAUDIUS +So you shall; +And where the offence is let the great axe fall. +I pray you, go with me. + + + +Exeunt + + +SCENE VI. Another room in the castle. +Enter HORATIO and a Servant + + +HORATIO +What are they that would speak with me? + + + +Servant +Sailors, sir: they say they have letters for you. + + + +HORATIO +Let them come in. +Exit Servant +I do not know from what part of the world +I should be greeted, if not from Lord Hamlet. + + + +Enter Sailors + + +First Sailor +God bless you, sir. + + + +HORATIO +Let him bless thee too. + + + +First Sailor +He shall, sir, an't please him. There's a letter for +you, sir; it comes from the ambassador that was +bound for England; if your name be Horatio, as I am +let to know it is. + + + +HORATIO +Reads 'Horatio, when thou shalt have overlooked +this, give these fellows some means to the king: +they have letters for him. Ere we were two days old +at sea, a pirate of very warlike appointment gave us +chase. Finding ourselves too slow of sail, we put on +a compelled valour, and in the grapple I boarded +them: on the instant they got clear of our ship; so +I alone became their prisoner. They have dealt with +me like thieves of mercy: but they knew what they +did; I am to do a good turn for them. Let the king +have the letters I have sent; and repair thou to me +with as much speed as thou wouldst fly death. I +have words to speak in thine ear will make thee +dumb; yet are they much too light for the bore of +the matter. These good fellows will bring thee +where I am. Rosencrantz and Guildenstern hold their +course for England: of them I have much to tell +thee. Farewell. +'He that thou knowest thine, HAMLET.' +Come, I will make you way for these your letters; +And do't the speedier, that you may direct me +To him from whom you brought them. + + + +Exeunt + + +SCENE VII. Another room in the castle. +Enter KING CLAUDIUS and LAERTES + + +KING CLAUDIUS +Now must your conscience my acquaintance seal, +And you must put me in your heart for friend, +Sith you have heard, and with a knowing ear, +That he which hath your noble father slain +Pursued my life. + + + +LAERTES +It well appears: but tell me +Why you proceeded not against these feats, +So crimeful and so capital in nature, +As by your safety, wisdom, all things else, +You mainly were stirr'd up. + + + +KING CLAUDIUS +O, for two special reasons; +Which may to you, perhaps, seem much unsinew'd, +But yet to me they are strong. The queen his mother +Lives almost by his looks; and for myself-- +My virtue or my plague, be it either which-- +She's so conjunctive to my life and soul, +That, as the star moves not but in his sphere, +I could not but by her. The other motive, +Why to a public count I might not go, +Is the great love the general gender bear him; +Who, dipping all his faults in their affection, +Would, like the spring that turneth wood to stone, +Convert his gyves to graces; so that my arrows, +Too slightly timber'd for so loud a wind, +Would have reverted to my bow again, +And not where I had aim'd them. + + + +LAERTES +And so have I a noble father lost; +A sister driven into desperate terms, +Whose worth, if praises may go back again, +Stood challenger on mount of all the age +For her perfections: but my revenge will come. + + + +KING CLAUDIUS +Break not your sleeps for that: you must not think +That we are made of stuff so flat and dull +That we can let our beard be shook with danger +And think it pastime. You shortly shall hear more: +I loved your father, and we love ourself; +And that, I hope, will teach you to imagine-- +Enter a Messenger +How now! what news? + + + +Messenger +Letters, my lord, from Hamlet: +This to your majesty; this to the queen. + + + +KING CLAUDIUS +From Hamlet! who brought them? + + + +Messenger +Sailors, my lord, they say; I saw them not: +They were given me by Claudio; he received them +Of him that brought them. + + + +KING CLAUDIUS +Laertes, you shall hear them. Leave us. +Exit Messenger +Reads +'High and mighty, You shall know I am set naked on +your kingdom. To-morrow shall I beg leave to see +your kingly eyes: when I shall, first asking your +pardon thereunto, recount the occasion of my sudden +and more strange return. 'HAMLET.' +What should this mean? Are all the rest come back? +Or is it some abuse, and no such thing? + + + +LAERTES +Know you the hand? + + + +KING CLAUDIUS +'Tis Hamlets character. 'Naked! +And in a postscript here, he says 'alone.' +Can you advise me? + + + +LAERTES +I'm lost in it, my lord. But let him come; +It warms the very sickness in my heart, +That I shall live and tell him to his teeth, +'Thus didest thou.' + + + +KING CLAUDIUS +If it be so, Laertes-- +As how should it be so? how otherwise?-- +Will you be ruled by me? + + + +LAERTES +Ay, my lord; +So you will not o'errule me to a peace. + + + +KING CLAUDIUS +To thine own peace. If he be now return'd, +As checking at his voyage, and that he means +No more to undertake it, I will work him +To an exploit, now ripe in my device, +Under the which he shall not choose but fall: +And for his death no wind of blame shall breathe, +But even his mother shall uncharge the practise +And call it accident. + + + +LAERTES +My lord, I will be ruled; +The rather, if you could devise it so +That I might be the organ. + + + +KING CLAUDIUS +It falls right. +You have been talk'd of since your travel much, +And that in Hamlet's hearing, for a quality +Wherein, they say, you shine: your sum of parts +Did not together pluck such envy from him +As did that one, and that, in my regard, +Of the unworthiest siege. + + + +LAERTES +What part is that, my lord? + + + +KING CLAUDIUS +A very riband in the cap of youth, +Yet needful too; for youth no less becomes +The light and careless livery that it wears +Than settled age his sables and his weeds, +Importing health and graveness. Two months since, +Here was a gentleman of Normandy:-- +I've seen myself, and served against, the French, +And they can well on horseback: but this gallant +Had witchcraft in't; he grew unto his seat; +And to such wondrous doing brought his horse, +As he had been incorpsed and demi-natured +With the brave beast: so far he topp'd my thought, +That I, in forgery of shapes and tricks, +Come short of what he did. + + + +LAERTES +A Norman was't? + + + +KING CLAUDIUS +A Norman. + + + +LAERTES +Upon my life, Lamond. + + + +KING CLAUDIUS +The very same. + + + +LAERTES +I know him well: he is the brooch indeed +And gem of all the nation. + + + +KING CLAUDIUS +He made confession of you, +And gave you such a masterly report +For art and exercise in your defence +And for your rapier most especially, +That he cried out, 'twould be a sight indeed, +If one could match you: the scrimers of their nation, +He swore, had had neither motion, guard, nor eye, +If you opposed them. Sir, this report of his +Did Hamlet so envenom with his envy +That he could nothing do but wish and beg +Your sudden coming o'er, to play with him. +Now, out of this,-- + + + +LAERTES +What out of this, my lord? + + + +KING CLAUDIUS +Laertes, was your father dear to you? +Or are you like the painting of a sorrow, +A face without a heart? + + + +LAERTES +Why ask you this? + + + +KING CLAUDIUS +Not that I think you did not love your father; +But that I know love is begun by time; +And that I see, in passages of proof, +Time qualifies the spark and fire of it. +There lives within the very flame of love +A kind of wick or snuff that will abate it; +And nothing is at a like goodness still; +For goodness, growing to a plurisy, +Dies in his own too much: that we would do +We should do when we would; for this 'would' changes +And hath abatements and delays as many +As there are tongues, are hands, are accidents; +And then this 'should' is like a spendthrift sigh, +That hurts by easing. But, to the quick o' the ulcer:-- +Hamlet comes back: what would you undertake, +To show yourself your father's son in deed +More than in words? + + + +LAERTES +To cut his throat i' the church. + + + +KING CLAUDIUS +No place, indeed, should murder sanctuarize; +Revenge should have no bounds. But, good Laertes, +Will you do this, keep close within your chamber. +Hamlet return'd shall know you are come home: +We'll put on those shall praise your excellence +And set a double varnish on the fame +The Frenchman gave you, bring you in fine together +And wager on your heads: he, being remiss, +Most generous and free from all contriving, +Will not peruse the foils; so that, with ease, +Or with a little shuffling, you may choose +A sword unbated, and in a pass of practise +Requite him for your father. + + + +LAERTES +I will do't: +And, for that purpose, I'll anoint my sword. +I bought an unction of a mountebank, +So mortal that, but dip a knife in it, +Where it draws blood no cataplasm so rare, +Collected from all simples that have virtue +Under the moon, can save the thing from death +That is but scratch'd withal: I'll touch my point +With this contagion, that, if I gall him slightly, +It may be death. + + + +KING CLAUDIUS +Let's further think of this; +Weigh what convenience both of time and means +May fit us to our shape: if this should fail, +And that our drift look through our bad performance, +'Twere better not assay'd: therefore this project +Should have a back or second, that might hold, +If this should blast in proof. Soft! let me see: +We'll make a solemn wager on your cunnings: I ha't. +When in your motion you are hot and dry-- +As make your bouts more violent to that end-- +And that he calls for drink, I'll have prepared him +A chalice for the nonce, whereon but sipping, +If he by chance escape your venom'd stuck, +Our purpose may hold there. +Enter QUEEN GERTRUDE +How now, sweet queen! + + + +QUEEN GERTRUDE +One woe doth tread upon another's heel, +So fast they follow; your sister's drown'd, Laertes. + + + +LAERTES +Drown'd! O, where? + + + +QUEEN GERTRUDE +There is a willow grows aslant a brook, +That shows his hoar leaves in the glassy stream; +There with fantastic garlands did she come +Of crow-flowers, nettles, daisies, and long purples +That liberal shepherds give a grosser name, +But our cold maids do dead men's fingers call them: +There, on the pendent boughs her coronet weeds +Clambering to hang, an envious sliver broke; +When down her weedy trophies and herself +Fell in the weeping brook. Her clothes spread wide; +And, mermaid-like, awhile they bore her up: +Which time she chanted snatches of old tunes; +As one incapable of her own distress, +Or like a creature native and indued +Unto that element: but long it could not be +Till that her garments, heavy with their drink, +Pull'd the poor wretch from her melodious lay +To muddy death. + + + +LAERTES +Alas, then, she is drown'd? + + + +QUEEN GERTRUDE +Drown'd, drown'd. + + + +LAERTES +Too much of water hast thou, poor Ophelia, +And therefore I forbid my tears: but yet +It is our trick; nature her custom holds, +Let shame say what it will: when these are gone, +The woman will be out. Adieu, my lord: +I have a speech of fire, that fain would blaze, +But that this folly douts it. + + + +Exit + + +KING CLAUDIUS +Let's follow, Gertrude: +How much I had to do to calm his rage! +Now fear I this will give it start again; +Therefore let's follow. + + + +Exeunt + + + + +ACT V + +SCENE I. A churchyard. +Enter two Clowns, with spades, &c + + +First Clown +Is she to be buried in Christian burial that +wilfully seeks her own salvation? + + + +Second Clown +I tell thee she is: and therefore make her grave +straight: the crowner hath sat on her, and finds it +Christian burial. + + + +First Clown +How can that be, unless she drowned herself in her +own defence? + + + +Second Clown +Why, 'tis found so. + + + +First Clown +It must be 'se offendendo;' it cannot be else. For +here lies the point: if I drown myself wittingly, +it argues an act: and an act hath three branches: it +is, to act, to do, to perform: argal, she drowned +herself wittingly. + + + +Second Clown +Nay, but hear you, goodman delver,-- + + + +First Clown +Give me leave. Here lies the water; good: here +stands the man; good; if the man go to this water, +and drown himself, it is, will he, nill he, he +goes,--mark you that; but if the water come to him +and drown him, he drowns not himself: argal, he +that is not guilty of his own death shortens not his own life. + + + +Second Clown +But is this law? + + + +First Clown +Ay, marry, is't; crowner's quest law. + + + +Second Clown +Will you ha' the truth on't? If this had not been +a gentlewoman, she should have been buried out o' +Christian burial. + + + +First Clown +Why, there thou say'st: and the more pity that +great folk should have countenance in this world to +drown or hang themselves, more than their even +Christian. Come, my spade. There is no ancient +gentleman but gardeners, ditchers, and grave-makers: +they hold up Adam's profession. + + + +Second Clown +Was he a gentleman? + + + +First Clown +He was the first that ever bore arms. + + + +Second Clown +Why, he had none. + + + +First Clown +What, art a heathen? How dost thou understand the +Scripture? The Scripture says 'Adam digged:' +could he dig without arms? I'll put another +question to thee: if thou answerest me not to the +purpose, confess thyself-- + + + +Second Clown +Go to. + + + +First Clown +What is he that builds stronger than either the +mason, the shipwright, or the carpenter? + + + +Second Clown +The gallows-maker; for that frame outlives a +thousand tenants. + + + +First Clown +I like thy wit well, in good faith: the gallows +does well; but how does it well? it does well to +those that do in: now thou dost ill to say the +gallows is built stronger than the church: argal, +the gallows may do well to thee. To't again, come. + + + +Second Clown +'Who builds stronger than a mason, a shipwright, or +a carpenter?' + + + +First Clown +Ay, tell me that, and unyoke. + + + +Second Clown +Marry, now I can tell. + + + +First Clown +To't. + + + +Second Clown +Mass, I cannot tell. + + + +Enter HAMLET and HORATIO, at a distance + + +First Clown +Cudgel thy brains no more about it, for your dull +ass will not mend his pace with beating; and, when +you are asked this question next, say 'a +grave-maker: 'the houses that he makes last till +doomsday. Go, get thee to Yaughan: fetch me a +stoup of liquor. +Exit Second Clown +He digs and sings +In youth, when I did love, did love, +Methought it was very sweet, +To contract, O, the time, for, ah, my behove, +O, methought, there was nothing meet. + + + +HAMLET +Has this fellow no feeling of his business, that he +sings at grave-making? + + + +HORATIO +Custom hath made it in him a property of easiness. + + + +HAMLET +'Tis e'en so: the hand of little employment hath +the daintier sense. + + + +First Clown +Sings +But age, with his stealing steps, +Hath claw'd me in his clutch, +And hath shipped me intil the land, +As if I had never been such. + + + +Throws up a skull + + +HAMLET +That skull had a tongue in it, and could sing once: +how the knave jowls it to the ground, as if it were +Cain's jaw-bone, that did the first murder! It +might be the pate of a politician, which this ass +now o'er-reaches; one that would circumvent God, +might it not? + + + +HORATIO +It might, my lord. + + + +HAMLET +Or of a courtier; which could say 'Good morrow, +sweet lord! How dost thou, good lord?' This might +be my lord such-a-one, that praised my lord +such-a-one's horse, when he meant to beg it; might it not? + + + +HORATIO +Ay, my lord. + + + +HAMLET +Why, e'en so: and now my Lady Worm's; chapless, and +knocked about the mazzard with a sexton's spade: +here's fine revolution, an we had the trick to +see't. Did these bones cost no more the breeding, +but to play at loggats with 'em? mine ache to think on't. + + + +First Clown +Sings +A pick-axe, and a spade, a spade, +For and a shrouding sheet: +O, a pit of clay for to be made +For such a guest is meet. + + + +Throws up another skull + + +HAMLET +There's another: why may not that be the skull of a +lawyer? Where be his quiddities now, his quillets, +his cases, his tenures, and his tricks? why does he +suffer this rude knave now to knock him about the +sconce with a dirty shovel, and will not tell him of +his action of battery? Hum! This fellow might be +in's time a great buyer of land, with his statutes, +his recognizances, his fines, his double vouchers, +his recoveries: is this the fine of his fines, and +the recovery of his recoveries, to have his fine +pate full of fine dirt? will his vouchers vouch him +no more of his purchases, and double ones too, than +the length and breadth of a pair of indentures? The +very conveyances of his lands will hardly lie in +this box; and must the inheritor himself have no more, ha? + + + +HORATIO +Not a jot more, my lord. + + + +HAMLET +Is not parchment made of sheepskins? + + + +HORATIO +Ay, my lord, and of calf-skins too. + + + +HAMLET +They are sheep and calves which seek out assurance +in that. I will speak to this fellow. Whose +grave's this, sirrah? + + + +First Clown +Mine, sir. +Sings +O, a pit of clay for to be made +For such a guest is meet. + + + +HAMLET +I think it be thine, indeed; for thou liest in't. + + + +First Clown +You lie out on't, sir, and therefore it is not +yours: for my part, I do not lie in't, and yet it is mine. + + + +HAMLET +'Thou dost lie in't, to be in't and say it is thine: +'tis for the dead, not for the quick; therefore thou liest. + + + +First Clown +'Tis a quick lie, sir; 'twill away gain, from me to +you. + + + +HAMLET +What man dost thou dig it for? + + + +First Clown +For no man, sir. + + + +HAMLET +What woman, then? + + + +First Clown +For none, neither. + + + +HAMLET +Who is to be buried in't? + + + +First Clown +One that was a woman, sir; but, rest her soul, she's dead. + + + +HAMLET +How absolute the knave is! we must speak by the +card, or equivocation will undo us. By the Lord, +Horatio, these three years I have taken a note of +it; the age is grown so picked that the toe of the +peasant comes so near the heel of the courtier, he +gaffs his kibe. How long hast thou been a +grave-maker? + + + +First Clown +Of all the days i' the year, I came to't that day +that our last king Hamlet overcame Fortinbras. + + + +HAMLET +How long is that since? + + + +First Clown +Cannot you tell that? every fool can tell that: it +was the very day that young Hamlet was born; he that +is mad, and sent into England. + + + +HAMLET +Ay, marry, why was he sent into England? + + + +First Clown +Why, because he was mad: he shall recover his wits +there; or, if he do not, it's no great matter there. + + + +HAMLET +Why? + + + +First Clown +'Twill, a not be seen in him there; there the men +are as mad as he. + + + +HAMLET +How came he mad? + + + +First Clown +Very strangely, they say. + + + +HAMLET +How strangely? + + + +First Clown +Faith, e'en with losing his wits. + + + +HAMLET +Upon what ground? + + + +First Clown +Why, here in Denmark: I have been sexton here, man +and boy, thirty years. + + + +HAMLET +How long will a man lie i' the earth ere he rot? + + + +First Clown +I' faith, if he be not rotten before he die--as we +have many pocky corses now-a-days, that will scarce +hold the laying in--he will last you some eight year +or nine year: a tanner will last you nine year. + + + +HAMLET +Why he more than another? + + + +First Clown +Why, sir, his hide is so tanned with his trade, that +he will keep out water a great while; and your water +is a sore decayer of your whoreson dead body. +Here's a skull now; this skull has lain in the earth +three and twenty years. + + + +HAMLET +Whose was it? + + + +First Clown +A whoreson mad fellow's it was: whose do you think it was? + + + +HAMLET +Nay, I know not. + + + +First Clown +A pestilence on him for a mad rogue! a' poured a +flagon of Rhenish on my head once. This same skull, +sir, was Yorick's skull, the king's jester. + + + +HAMLET +This? + + + +First Clown +E'en that. + + + +HAMLET +Let me see. +Takes the skull +Alas, poor Yorick! I knew him, Horatio: a fellow +of infinite jest, of most excellent fancy: he hath +borne me on his back a thousand times; and now, how +abhorred in my imagination it is! my gorge rims at +it. Here hung those lips that I have kissed I know +not how oft. Where be your gibes now? your +gambols? your songs? your flashes of merriment, +that were wont to set the table on a roar? Not one +now, to mock your own grinning? quite chap-fallen? +Now get you to my lady's chamber, and tell her, let +her paint an inch thick, to this favour she must +come; make her laugh at that. Prithee, Horatio, tell +me one thing. + + + +HORATIO +What's that, my lord? + + + +HAMLET +Dost thou think Alexander looked o' this fashion i' +the earth? + + + +HORATIO +E'en so. + + + +HAMLET +And smelt so? pah! + + + +Puts down the skull + + +HORATIO +E'en so, my lord. + + + +HAMLET +To what base uses we may return, Horatio! Why may +not imagination trace the noble dust of Alexander, +till he find it stopping a bung-hole? + + + +HORATIO +'Twere to consider too curiously, to consider so. + + + +HAMLET +No, faith, not a jot; but to follow him thither with +modesty enough, and likelihood to lead it: as +thus: Alexander died, Alexander was buried, +Alexander returneth into dust; the dust is earth; of +earth we make loam; and why of that loam, whereto he +was converted, might they not stop a beer-barrel? +Imperious Caesar, dead and turn'd to clay, +Might stop a hole to keep the wind away: +O, that that earth, which kept the world in awe, +Should patch a wall to expel the winter flaw! +But soft! but soft! aside: here comes the king. +Enter Priest, &c. in procession; the Corpse of +OPHELIA, LAERTES and Mourners following; KING +CLAUDIUS, QUEEN GERTRUDE, their trains, &c +The queen, the courtiers: who is this they follow? +And with such maimed rites? This doth betoken +The corse they follow did with desperate hand +Fordo its own life: 'twas of some estate. +Couch we awhile, and mark. + + + +Retiring with HORATIO + + +LAERTES +What ceremony else? + + + +HAMLET +That is Laertes, +A very noble youth: mark. + + + +LAERTES +What ceremony else? + + + +First Priest +Her obsequies have been as far enlarged +As we have warrantise: her death was doubtful; +And, but that great command o'ersways the order, +She should in ground unsanctified have lodged +Till the last trumpet: for charitable prayers, +Shards, flints and pebbles should be thrown on her; +Yet here she is allow'd her virgin crants, +Her maiden strewments and the bringing home +Of bell and burial. + + + +LAERTES +Must there no more be done? + + + +First Priest +No more be done: +We should profane the service of the dead +To sing a requiem and such rest to her +As to peace-parted souls. + + + +LAERTES +Lay her i' the earth: +And from her fair and unpolluted flesh +May violets spring! I tell thee, churlish priest, +A ministering angel shall my sister be, +When thou liest howling. + + + +HAMLET +What, the fair Ophelia! + + + +QUEEN GERTRUDE +Sweets to the sweet: farewell! +Scattering flowers +I hoped thou shouldst have been my Hamlet's wife; +I thought thy bride-bed to have deck'd, sweet maid, +And not have strew'd thy grave. + + + +LAERTES +O, treble woe +Fall ten times treble on that cursed head, +Whose wicked deed thy most ingenious sense +Deprived thee of! Hold off the earth awhile, +Till I have caught her once more in mine arms: +Leaps into the grave +Now pile your dust upon the quick and dead, +Till of this flat a mountain you have made, +To o'ertop old Pelion, or the skyish head +Of blue Olympus. + + + +HAMLET +Advancing What is he whose grief +Bears such an emphasis? whose phrase of sorrow +Conjures the wandering stars, and makes them stand +Like wonder-wounded hearers? This is I, +Hamlet the Dane. + + + +Leaps into the grave + + +LAERTES +The devil take thy soul! + + + +Grappling with him + + +HAMLET +Thou pray'st not well. +I prithee, take thy fingers from my throat; +For, though I am not splenitive and rash, +Yet have I something in me dangerous, +Which let thy wiseness fear: hold off thy hand. + + + +KING CLAUDIUS +Pluck them asunder. + + + +QUEEN GERTRUDE +Hamlet, Hamlet! + + + +All +Gentlemen,-- + + + +HORATIO +Good my lord, be quiet. + + + +The Attendants part them, and they come out of the grave + + +HAMLET +Why I will fight with him upon this theme +Until my eyelids will no longer wag. + + + +QUEEN GERTRUDE +O my son, what theme? + + + +HAMLET +I loved Ophelia: forty thousand brothers +Could not, with all their quantity of love, +Make up my sum. What wilt thou do for her? + + + +KING CLAUDIUS +O, he is mad, Laertes. + + + +QUEEN GERTRUDE +For love of God, forbear him. + + + +HAMLET +'Swounds, show me what thou'lt do: +Woo't weep? woo't fight? woo't fast? woo't tear thyself? +Woo't drink up eisel? eat a crocodile? +I'll do't. Dost thou come here to whine? +To outface me with leaping in her grave? +Be buried quick with her, and so will I: +And, if thou prate of mountains, let them throw +Millions of acres on us, till our ground, +Singeing his pate against the burning zone, +Make Ossa like a wart! Nay, an thou'lt mouth, +I'll rant as well as thou. + + + +QUEEN GERTRUDE +This is mere madness: +And thus awhile the fit will work on him; +Anon, as patient as the female dove, +When that her golden couplets are disclosed, +His silence will sit drooping. + + + +HAMLET +Hear you, sir; +What is the reason that you use me thus? +I loved you ever: but it is no matter; +Let Hercules himself do what he may, +The cat will mew and dog will have his day. + + + +Exit + + +KING CLAUDIUS +I pray you, good Horatio, wait upon him. +Exit HORATIO +To LAERTES +Strengthen your patience in our last night's speech; +We'll put the matter to the present push. +Good Gertrude, set some watch over your son. +This grave shall have a living monument: +An hour of quiet shortly shall we see; +Till then, in patience our proceeding be. + + + +Exeunt + + +SCENE II. A hall in the castle. +Enter HAMLET and HORATIO + + +HAMLET +So much for this, sir: now shall you see the other; +You do remember all the circumstance? + + + +HORATIO +Remember it, my lord? + + + +HAMLET +Sir, in my heart there was a kind of fighting, +That would not let me sleep: methought I lay +Worse than the mutines in the bilboes. Rashly, +And praised be rashness for it, let us know, +Our indiscretion sometimes serves us well, +When our deep plots do pall: and that should teach us +There's a divinity that shapes our ends, +Rough-hew them how we will,-- + + + +HORATIO +That is most certain. + + + +HAMLET +Up from my cabin, +My sea-gown scarf'd about me, in the dark +Groped I to find out them; had my desire. +Finger'd their packet, and in fine withdrew +To mine own room again; making so bold, +My fears forgetting manners, to unseal +Their grand commission; where I found, Horatio,-- +O royal knavery!--an exact command, +Larded with many several sorts of reasons +Importing Denmark's health and England's too, +With, ho! such bugs and goblins in my life, +That, on the supervise, no leisure bated, +No, not to stay the grinding of the axe, +My head should be struck off. + + + +HORATIO +Is't possible? + + + +HAMLET +Here's the commission: read it at more leisure. +But wilt thou hear me how I did proceed? + + + +HORATIO +I beseech you. + + + +HAMLET +Being thus be-netted round with villanies,-- +Ere I could make a prologue to my brains, +They had begun the play--I sat me down, +Devised a new commission, wrote it fair: +I once did hold it, as our statists do, +A baseness to write fair and labour'd much +How to forget that learning, but, sir, now +It did me yeoman's service: wilt thou know +The effect of what I wrote? + + + +HORATIO +Ay, good my lord. + + + +HAMLET +An earnest conjuration from the king, +As England was his faithful tributary, +As love between them like the palm might flourish, +As peace should stiff her wheaten garland wear +And stand a comma 'tween their amities, +And many such-like 'As'es of great charge, +That, on the view and knowing of these contents, +Without debatement further, more or less, +He should the bearers put to sudden death, +Not shriving-time allow'd. + + + +HORATIO +How was this seal'd? + + + +HAMLET +Why, even in that was heaven ordinant. +I had my father's signet in my purse, +Which was the model of that Danish seal; +Folded the writ up in form of the other, +Subscribed it, gave't the impression, placed it safely, +The changeling never known. Now, the next day +Was our sea-fight; and what to this was sequent +Thou know'st already. + + + +HORATIO +So Guildenstern and Rosencrantz go to't. + + + +HAMLET +Why, man, they did make love to this employment; +They are not near my conscience; their defeat +Does by their own insinuation grow: +'Tis dangerous when the baser nature comes +Between the pass and fell incensed points +Of mighty opposites. + + + +HORATIO +Why, what a king is this! + + + +HAMLET +Does it not, think'st thee, stand me now upon-- +He that hath kill'd my king and whored my mother, +Popp'd in between the election and my hopes, +Thrown out his angle for my proper life, +And with such cozenage--is't not perfect conscience, +To quit him with this arm? and is't not to be damn'd, +To let this canker of our nature come +In further evil? + + + +HORATIO +It must be shortly known to him from England +What is the issue of the business there. + + + +HAMLET +It will be short: the interim is mine; +And a man's life's no more than to say 'One.' +But I am very sorry, good Horatio, +That to Laertes I forgot myself; +For, by the image of my cause, I see +The portraiture of his: I'll court his favours. +But, sure, the bravery of his grief did put me +Into a towering passion. + + + +HORATIO +Peace! who comes here? + + + +Enter OSRIC + + +OSRIC +Your lordship is right welcome back to Denmark. + + + +HAMLET +I humbly thank you, sir. Dost know this water-fly? + + + +HORATIO +No, my good lord. + + + +HAMLET +Thy state is the more gracious; for 'tis a vice to +know him. He hath much land, and fertile: let a +beast be lord of beasts, and his crib shall stand at +the king's mess: 'tis a chough; but, as I say, +spacious in the possession of dirt. + + + +OSRIC +Sweet lord, if your lordship were at leisure, I +should impart a thing to you from his majesty. + + + +HAMLET +I will receive it, sir, with all diligence of +spirit. Put your bonnet to his right use; 'tis for the head. + + + +OSRIC +I thank your lordship, it is very hot. + + + +HAMLET +No, believe me, 'tis very cold; the wind is +northerly. + + + +OSRIC +It is indifferent cold, my lord, indeed. + + + +HAMLET +But yet methinks it is very sultry and hot for my +complexion. + + + +OSRIC +Exceedingly, my lord; it is very sultry,--as +'twere,--I cannot tell how. But, my lord, his +majesty bade me signify to you that he has laid a +great wager on your head: sir, this is the matter,-- + + + +HAMLET +I beseech you, remember-- + + + +HAMLET moves him to put on his hat + + +OSRIC +Nay, good my lord; for mine ease, in good faith. +Sir, here is newly come to court Laertes; believe +me, an absolute gentleman, full of most excellent +differences, of very soft society and great showing: +indeed, to speak feelingly of him, he is the card or +calendar of gentry, for you shall find in him the +continent of what part a gentleman would see. + + + +HAMLET +Sir, his definement suffers no perdition in you; +though, I know, to divide him inventorially would +dizzy the arithmetic of memory, and yet but yaw +neither, in respect of his quick sail. But, in the +verity of extolment, I take him to be a soul of +great article; and his infusion of such dearth and +rareness, as, to make true diction of him, his +semblable is his mirror; and who else would trace +him, his umbrage, nothing more. + + + +OSRIC +Your lordship speaks most infallibly of him. + + + +HAMLET +The concernancy, sir? why do we wrap the gentleman +in our more rawer breath? + + + +OSRIC +Sir? + + + +HORATIO +Is't not possible to understand in another tongue? +You will do't, sir, really. + + + +HAMLET +What imports the nomination of this gentleman? + + + +OSRIC +Of Laertes? + + + +HORATIO +His purse is empty already; all's golden words are spent. + + + +HAMLET +Of him, sir. + + + +OSRIC +I know you are not ignorant-- + + + +HAMLET +I would you did, sir; yet, in faith, if you did, +it would not much approve me. Well, sir? + + + +OSRIC +You are not ignorant of what excellence Laertes is-- + + + +HAMLET +I dare not confess that, lest I should compare with +him in excellence; but, to know a man well, were to +know himself. + + + +OSRIC +I mean, sir, for his weapon; but in the imputation +laid on him by them, in his meed he's unfellowed. + + + +HAMLET +What's his weapon? + + + +OSRIC +Rapier and dagger. + + + +HAMLET +That's two of his weapons: but, well. + + + +OSRIC +The king, sir, hath wagered with him six Barbary +horses: against the which he has imponed, as I take +it, six French rapiers and poniards, with their +assigns, as girdle, hangers, and so: three of the +carriages, in faith, are very dear to fancy, very +responsive to the hilts, most delicate carriages, +and of very liberal conceit. + + + +HAMLET +What call you the carriages? + + + +HORATIO +I knew you must be edified by the margent ere you had done. + + + +OSRIC +The carriages, sir, are the hangers. + + + +HAMLET +The phrase would be more german to the matter, if we +could carry cannon by our sides: I would it might +be hangers till then. But, on: six Barbary horses +against six French swords, their assigns, and three +liberal-conceited carriages; that's the French bet +against the Danish. Why is this 'imponed,' as you call it? + + + +OSRIC +The king, sir, hath laid, that in a dozen passes +between yourself and him, he shall not exceed you +three hits: he hath laid on twelve for nine; and it +would come to immediate trial, if your lordship +would vouchsafe the answer. + + + +HAMLET +How if I answer 'no'? + + + +OSRIC +I mean, my lord, the opposition of your person in trial. + + + +HAMLET +Sir, I will walk here in the hall: if it please his +majesty, 'tis the breathing time of day with me; let +the foils be brought, the gentleman willing, and the +king hold his purpose, I will win for him an I can; +if not, I will gain nothing but my shame and the odd hits. + + + +OSRIC +Shall I re-deliver you e'en so? + + + +HAMLET +To this effect, sir; after what flourish your nature will. + + + +OSRIC +I commend my duty to your lordship. + + + +HAMLET +Yours, yours. +Exit OSRIC +He does well to commend it himself; there are no +tongues else for's turn. + + + +HORATIO +This lapwing runs away with the shell on his head. + + + +HAMLET +He did comply with his dug, before he sucked it. +Thus has he--and many more of the same bevy that I +know the dressy age dotes on--only got the tune of +the time and outward habit of encounter; a kind of +yesty collection, which carries them through and +through the most fond and winnowed opinions; and do +but blow them to their trial, the bubbles are out. + + + +Enter a Lord + + +Lord +My lord, his majesty commended him to you by young +Osric, who brings back to him that you attend him in +the hall: he sends to know if your pleasure hold to +play with Laertes, or that you will take longer time. + + + +HAMLET +I am constant to my purpose; they follow the king's +pleasure: if his fitness speaks, mine is ready; now +or whensoever, provided I be so able as now. + + + +Lord +The king and queen and all are coming down. + + + +HAMLET +In happy time. + + + +Lord +The queen desires you to use some gentle +entertainment to Laertes before you fall to play. + + + +HAMLET +She well instructs me. + + + +Exit Lord + + +HORATIO +You will lose this wager, my lord. + + + +HAMLET +I do not think so: since he went into France, I +have been in continual practise: I shall win at the +odds. But thou wouldst not think how ill all's here +about my heart: but it is no matter. + + + +HORATIO +Nay, good my lord,-- + + + +HAMLET +It is but foolery; but it is such a kind of +gain-giving, as would perhaps trouble a woman. + + + +HORATIO +If your mind dislike any thing, obey it: I will +forestall their repair hither, and say you are not +fit. + + + +HAMLET +Not a whit, we defy augury: there's a special +providence in the fall of a sparrow. If it be now, +'tis not to come; if it be not to come, it will be +now; if it be not now, yet it will come: the +readiness is all: since no man has aught of what he +leaves, what is't to leave betimes? + + + +Enter KING CLAUDIUS, QUEEN GERTRUDE, LAERTES, +Lords, OSRIC, and Attendants with foils, &c + + +KING CLAUDIUS +Come, Hamlet, come, and take this hand from me. + + + +KING CLAUDIUS puts LAERTES' hand into HAMLET's + + +HAMLET +Give me your pardon, sir: I've done you wrong; +But pardon't, as you are a gentleman. +This presence knows, +And you must needs have heard, how I am punish'd +With sore distraction. What I have done, +That might your nature, honour and exception +Roughly awake, I here proclaim was madness. +Was't Hamlet wrong'd Laertes? Never Hamlet: +If Hamlet from himself be ta'en away, +And when he's not himself does wrong Laertes, +Then Hamlet does it not, Hamlet denies it. +Who does it, then? His madness: if't be so, +Hamlet is of the faction that is wrong'd; +His madness is poor Hamlet's enemy. +Sir, in this audience, +Let my disclaiming from a purposed evil +Free me so far in your most generous thoughts, +That I have shot mine arrow o'er the house, +And hurt my brother. + + + +LAERTES +I am satisfied in nature, +Whose motive, in this case, should stir me most +To my revenge: but in my terms of honour +I stand aloof; and will no reconcilement, +Till by some elder masters, of known honour, +I have a voice and precedent of peace, +To keep my name ungored. But till that time, +I do receive your offer'd love like love, +And will not wrong it. + + + +HAMLET +I embrace it freely; +And will this brother's wager frankly play. +Give us the foils. Come on. + + + +LAERTES +Come, one for me. + + + +HAMLET +I'll be your foil, Laertes: in mine ignorance +Your skill shall, like a star i' the darkest night, +Stick fiery off indeed. + + + +LAERTES +You mock me, sir. + + + +HAMLET +No, by this hand. + + + +KING CLAUDIUS +Give them the foils, young Osric. Cousin Hamlet, +You know the wager? + + + +HAMLET +Very well, my lord +Your grace hath laid the odds o' the weaker side. + + + +KING CLAUDIUS +I do not fear it; I have seen you both: +But since he is better'd, we have therefore odds. + + + +LAERTES +This is too heavy, let me see another. + + + +HAMLET +This likes me well. These foils have all a length? + + + +They prepare to play + + +OSRIC +Ay, my good lord. + + + +KING CLAUDIUS +Set me the stoops of wine upon that table. +If Hamlet give the first or second hit, +Or quit in answer of the third exchange, +Let all the battlements their ordnance fire: +The king shall drink to Hamlet's better breath; +And in the cup an union shall he throw, +Richer than that which four successive kings +In Denmark's crown have worn. Give me the cups; +And let the kettle to the trumpet speak, +The trumpet to the cannoneer without, +The cannons to the heavens, the heavens to earth, +'Now the king dunks to Hamlet.' Come, begin: +And you, the judges, bear a wary eye. + + + +HAMLET +Come on, sir. + + + +LAERTES +Come, my lord. + + + +They play + + +HAMLET +One. + + + +LAERTES +No. + + + +HAMLET +Judgment. + + + +OSRIC +A hit, a very palpable hit. + + + +LAERTES +Well; again. + + + +KING CLAUDIUS +Stay; give me drink. Hamlet, this pearl is thine; +Here's to thy health. +Trumpets sound, and cannon shot off within +Give him the cup. + + + +HAMLET +I'll play this bout first; set it by awhile. Come. +They play +Another hit; what say you? + + + +LAERTES +A touch, a touch, I do confess. + + + +KING CLAUDIUS +Our son shall win. + + + +QUEEN GERTRUDE +He's fat, and scant of breath. +Here, Hamlet, take my napkin, rub thy brows; +The queen carouses to thy fortune, Hamlet. + + + +HAMLET +Good madam! + + + +KING CLAUDIUS +Gertrude, do not drink. + + + +QUEEN GERTRUDE +I will, my lord; I pray you, pardon me. + + + +KING CLAUDIUS +Aside It is the poison'd cup: it is too late. + + + +HAMLET +I dare not drink yet, madam; by and by. + + + +QUEEN GERTRUDE +Come, let me wipe thy face. + + + +LAERTES +My lord, I'll hit him now. + + + +KING CLAUDIUS +I do not think't. + + + +LAERTES +Aside And yet 'tis almost 'gainst my conscience. + + + +HAMLET +Come, for the third, Laertes: you but dally; +I pray you, pass with your best violence; +I am afeard you make a wanton of me. + + + +LAERTES +Say you so? come on. + + + +They play + + +OSRIC +Nothing, neither way. + + + +LAERTES +Have at you now! + + + +LAERTES wounds HAMLET; then in scuffling, they +change rapiers, and HAMLET wounds LAERTES + + +KING CLAUDIUS +Part them; they are incensed. + + + +HAMLET +Nay, come, again. + + + +QUEEN GERTRUDE falls + + +OSRIC +Look to the queen there, ho! + + + +HORATIO +They bleed on both sides. How is it, my lord? + + + +OSRIC +How is't, Laertes? + + + +LAERTES +Why, as a woodcock to mine own springe, Osric; +I am justly kill'd with mine own treachery. + + + +HAMLET +How does the queen? + + + +KING CLAUDIUS +She swounds to see them bleed. + + + +QUEEN GERTRUDE +No, no, the drink, the drink,--O my dear Hamlet,-- +The drink, the drink! I am poison'd. + + + +Dies + + +HAMLET +O villany! Ho! let the door be lock'd: +Treachery! Seek it out. + + + +LAERTES +It is here, Hamlet: Hamlet, thou art slain; +No medicine in the world can do thee good; +In thee there is not half an hour of life; +The treacherous instrument is in thy hand, +Unbated and envenom'd: the foul practise +Hath turn'd itself on me lo, here I lie, +Never to rise again: thy mother's poison'd: +I can no more: the king, the king's to blame. + + + +HAMLET +The point!--envenom'd too! +Then, venom, to thy work. + + + +Stabs KING CLAUDIUS + + +All +Treason! treason! + + + +KING CLAUDIUS +O, yet defend me, friends; I am but hurt. + + + +HAMLET +Here, thou incestuous, murderous, damned Dane, +Drink off this potion. Is thy union here? +Follow my mother. + + + +KING CLAUDIUS dies + + +LAERTES +He is justly served; +It is a poison temper'd by himself. +Exchange forgiveness with me, noble Hamlet: +Mine and my father's death come not upon thee, +Nor thine on me. + + + +Dies + + +HAMLET +Heaven make thee free of it! I follow thee. +I am dead, Horatio. Wretched queen, adieu! +You that look pale and tremble at this chance, +That are but mutes or audience to this act, +Had I but time--as this fell sergeant, death, +Is strict in his arrest--O, I could tell you-- +But let it be. Horatio, I am dead; +Thou livest; report me and my cause aright +To the unsatisfied. + + + +HORATIO +Never believe it: +I am more an antique Roman than a Dane: +Here's yet some liquor left. + + + +HAMLET +As thou'rt a man, +Give me the cup: let go; by heaven, I'll have't. +O good Horatio, what a wounded name, +Things standing thus unknown, shall live behind me! +If thou didst ever hold me in thy heart +Absent thee from felicity awhile, +And in this harsh world draw thy breath in pain, +To tell my story. +March afar off, and shot within +What warlike noise is this? + + + +OSRIC +Young Fortinbras, with conquest come from Poland, +To the ambassadors of England gives +This warlike volley. + + + +HAMLET +O, I die, Horatio; +The potent poison quite o'er-crows my spirit: +I cannot live to hear the news from England; +But I do prophesy the election lights +On Fortinbras: he has my dying voice; +So tell him, with the occurrents, more and less, +Which have solicited. The rest is silence. + + + +Dies + + +HORATIO +Now cracks a noble heart. Good night sweet prince: +And flights of angels sing thee to thy rest! +Why does the drum come hither? + + +March within +Enter FORTINBRAS, the English Ambassadors, +and others + + +PRINCE FORTINBRAS +Where is this sight? + + + +HORATIO +What is it ye would see? +If aught of woe or wonder, cease your search. + + + +PRINCE FORTINBRAS +This quarry cries on havoc. O proud death, +What feast is toward in thine eternal cell, +That thou so many princes at a shot +So bloodily hast struck? + + + +First Ambassador +The sight is dismal; +And our affairs from England come too late: +The ears are senseless that should give us hearing, +To tell him his commandment is fulfill'd, +That Rosencrantz and Guildenstern are dead: +Where should we have our thanks? + + + +HORATIO +Not from his mouth, +Had it the ability of life to thank you: +He never gave commandment for their death. +But since, so jump upon this bloody question, +You from the Polack wars, and you from England, +Are here arrived give order that these bodies +High on a stage be placed to the view; +And let me speak to the yet unknowing world +How these things came about: so shall you hear +Of carnal, bloody, and unnatural acts, +Of accidental judgments, casual slaughters, +Of deaths put on by cunning and forced cause, +And, in this upshot, purposes mistook +Fall'n on the inventors' reads: all this can I +Truly deliver. + + + +PRINCE FORTINBRAS +Let us haste to hear it, +And call the noblest to the audience. +For me, with sorrow I embrace my fortune: +I have some rights of memory in this kingdom, +Which now to claim my vantage doth invite me. + + + +HORATIO +Of that I shall have also cause to speak, +And from his mouth whose voice will draw on more; +But let this same be presently perform'd, +Even while men's minds are wild; lest more mischance +On plots and errors, happen. + + + +PRINCE FORTINBRAS +Let four captains +Bear Hamlet, like a soldier, to the stage; +For he was likely, had he been put on, +To have proved most royally: and, for his passage, +The soldiers' music and the rites of war +Speak loudly for him. +Take up the bodies: such a sight as this +Becomes the field, but here shows much amiss. +Go, bid the soldiers shoot. + + + +A dead march. Exeunt, bearing off the dead +bodies; after which a peal of ordnance is shot off + + +
diff --git a/core/.gitignore b/core/.gitignore new file mode 100644 index 0000000..cb9f69b --- /dev/null +++ b/core/.gitignore @@ -0,0 +1,2 @@ +/build +/out \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 0000000..d7f506b --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,25 @@ +sourceSets { + main { + java { + srcDir 'src/java' + } + } +} + +sourceCompatibility = '1.6' +targetCompatibility = '1.6' + +dependencies { + compileOnly 'jaxen:jaxen:1.1.6' +} + +archivesBaseName = 'jdom' + +task sourceJar(type: Jar) { + classifier 'sources' + from sourceSets.main.java +} + +artifacts { + archives sourceJar +} \ No newline at end of file diff --git a/core/package/META-INF/MANIFEST.MF b/core/package/META-INF/MANIFEST.MF new file mode 100644 index 0000000..119c8df --- /dev/null +++ b/core/package/META-INF/MANIFEST.MF @@ -0,0 +1,57 @@ +Manifest-Version: 1.0 + +Name: org/jdom2/ +Specification-Title: JDOM Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org +Implementation-Title: org.jdom +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org + +Name: org/jdom2/input/ +Specification-Title: JDOM Input Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org2 +Implementation-Title: org.jdom.input +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org + +Name: org/jdom2/output/ +Specification-Title: JDOM Output Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org +Implementation-Title: org.jdom.output +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org + +Name: org/jdom2/adapters/ +Specification-Title: JDOM Adapter Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org +Implementation-Title: org.jdom.adapters +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org + +Name: org/jdom2/filter/ +Specification-Title: JDOM Filter Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org +Implementation-Title: org.jdom.filter +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org + +Name: org/jdom2/transform/ +Specification-Title: JDOM Transformation Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org +Implementation-Title: org.jdom.transform +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org + +Name: org/jdom2/xpath/ +Specification-Title: JDOM XPath Classes +Specification-Version: @version.spec@ +Specification-Vendor: jdom.org +Implementation-Title: org.jdom.xpath +Implementation-Version: @version.impl@ +Implementation-Vendor: jdom.org diff --git a/core/package/META-INF/jdom-info.xml b/core/package/META-INF/jdom-info.xml new file mode 100644 index 0000000..cfb3738 --- /dev/null +++ b/core/package/META-INF/jdom-info.xml @@ -0,0 +1,98 @@ + + + JDOM + @version@, built @date@ + + JDOM is a Java-oriented object model which models XML documents. + It provides a Java-centric means of generating and manipulating + XML documents. While JDOM interoperates well with existing + standards such as the Simple API for XML (SAX) and the Document + Object Model (DOM), it is not an abstraction layer or + enhancement to those APIs. Rather, it seeks to provide a robust, + light-weight means of reading and writing XML data without the + complex and memory-consumptive options that current API + offerings provide. + + 2000-@year@, Jason Hunter + BSD/Apache style, see LICENSE.txt + See the jdom-interest mailing list at jdom.org, + searchable at http://jdom.markmail.org + http://www.jdom.org/ + + + Jason Hunter (primary, co-creator) + + + Brett McLaughlin (co-creator) + + + Rolf Lear (primary, maintainer) + + + Steven Gould + + + Alex Chaffee + + + Jon Baer + + + Elliotte Rusty Harold + + + Dan Schaffer + + + Fred Trimble + + + Jason Reid + + + Kevin Regan + + + Lucas Gonze + + + Matthew Merlo + + + Philip Nelson + + + Wesley Biggs + + + Wolfgang Werner + + + Yusuf Goolamabbas + + + Brad Huffman + + + Victor Toni + + + + + diff --git a/core/samples/DescendantDemo.java b/core/samples/DescendantDemo.java new file mode 100644 index 0000000..9bb2ac4 --- /dev/null +++ b/core/samples/DescendantDemo.java @@ -0,0 +1,144 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + + +import java.util.*; + +import org.jdom.*; +import org.jdom.filter2.ElementFilter; +import org.jdom.filter2.Filters; +import org.jdom.input.*; +import org.jdom.output.*; + +/** + * Demonstrates the use of {@link Parent#getDescendants}. + */ +@SuppressWarnings("javadoc") +public class DescendantDemo { + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println( + "Usage: java DescendantDemo file1.xml [file2.xml ... ]"); + return; + } + + final SAXBuilder builder = new SAXBuilder(); + for (final String fname : args) { + System.out.println("Processing file " + fname); + // The String argument is considered to be a SystemID which can + // be a URL, File name, a relative file path, etc. + final Document doc = builder.build(fname); + + System.out.println("All content:"); + Iterator itr = doc.getDescendants(); + while (itr.hasNext()) { + Content c = itr.next(); + // c.toString() gives a very brief output. + System.out.println(c.toString()); + } + + System.out.println(); + System.out.println("Only elements:"); + itr = doc.getDescendants(new ElementFilter()); + while (itr.hasNext()) { + Content c = itr.next(); + System.out.println(c); + } + + System.out.println(); + System.out.println("Everything that's not an element:"); + itr = doc.getDescendants(new ElementFilter().negate().refine(Filters.content())); + while (itr.hasNext()) { + Content c = itr.next(); + System.out.println(c); + } + + System.out.println(); + System.out.println("Only elements with localname of 'servlet':"); + itr = doc.getDescendants(new ElementFilter("servlet")); + while (itr.hasNext()) { + Content c = itr.next(); + System.out.println(c); + } + + System.out.println(); + System.out.println( + "Only elements with localname of servlet-name or servlet-class:"); + itr = doc.getDescendants(new ElementFilter("servlet-name") + .or(new ElementFilter("servlet-class"))); + while (itr.hasNext()) { + Content c = itr.next(); + System.out.println(c); + } + + System.out.println(); + System.out.println("Remove elements with localname of servlet:"); + Iteratorite = doc.getDescendants(new ElementFilter("servlet")); + while (ite.hasNext()) { + Element e = ite.next(); + System.out.println(e); + ite.remove(); + } + + System.out.println(); + System.out.println("Dump the remaining document to console:"); + XMLOutputter outp = new XMLOutputter(); + outp.output(doc, System.out); + System.out.println(); + System.out.println(); + } + } +} diff --git a/core/samples/SAXBuilderDemo.java b/core/samples/SAXBuilderDemo.java new file mode 100644 index 0000000..1876b1b --- /dev/null +++ b/core/samples/SAXBuilderDemo.java @@ -0,0 +1,112 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + + +import java.io.IOException; + +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter; + +/** + *

SAXBuilderDemo demonstrates how to + * build a JDOM Document using a SAX parser. + *

+ * + * @author Brett McLaughlin + * @author Jason Hunter + * @version 1.0 + */ +public class SAXBuilderDemo { + + /** + *

+ * This provides a static entry point for creating a JDOM + * {@link Document} object using a SAX 2.0 + * parser (an XMLReader implementation). + *

+ * + * @param args String[] list of files to parse + */ + public static void main(String[] args) { + if (args.length < 1) { + System.err.println( + "Usage: java SAXBuilderDemo file1.xml [ file2.xml [ ... ] ]"); + return; + } + + // Used to parse documents + final SAXBuilder builder = new SAXBuilder(); + // used to output documents as String-based XML. + // in this case the output will be 'pretty' formatted. + final XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat()); + + for (String filename : args) { + // Create an instance of the tester and test + try { + + final Document doc = builder.build(filename); + + outputter.output(doc, System.out); + } catch (JDOMException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/core/samples/WarReader.java b/core/samples/WarReader.java new file mode 100644 index 0000000..cf8acbe --- /dev/null +++ b/core/samples/WarReader.java @@ -0,0 +1,124 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.util.List; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +/** + *

WarReader demonstrates how to + * read a Servlet 2.2 Web Archive file with JDOM. + *

+ * + * @author Brett McLaughlin, Jason Hunter + * @version 1.0 + */ +@SuppressWarnings("javadoc") +public class WarReader { + + public static void main(String[] args) throws IOException, JDOMException { + if (args.length != 1) { + System.err.println("Usage: java WarReader [web.xml]"); + return; + } + String filename = args[0]; + PrintStream out = System.out; + + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(new File(filename)); + + // Get the root element + Element root = doc.getRootElement(); + + // Print servlet information + List servlets = root.getChildren("servlet"); + out.println("This WAR has "+ servlets.size() +" registered servlets:"); + for (Element servlet : servlets) { + out.print("\t" + servlet.getChild("servlet-name") + .getTextTrim() + + " for " + servlet.getChild("servlet-class") + .getTextTrim()); + List initParams = servlet.getChildren("init-param"); + out.println(" (it has " + initParams.size() + " init params)"); + } + + // Print security role information + List securityRoles = root.getChildren("security-role"); + if (securityRoles.size() == 0) { + out.println("This WAR contains no roles"); + } else { + Element securityRole = securityRoles.get(0); + List roleNames = securityRole.getChildren("role-name"); + out.println("This WAR contains " + roleNames.size() + " roles:"); + for (Element e : roleNames) { + out.println("\t" + e.getTextTrim()); + } + } + + // Print distributed information (notice this is out of order) + if (root.getChildren("distributed").isEmpty()) { + out.println("This WAR is not distributed"); + } else { + out.println("This WAR is distributed"); + } + } +} diff --git a/core/samples/XPathReader.java b/core/samples/XPathReader.java new file mode 100644 index 0000000..7e8f3b7 --- /dev/null +++ b/core/samples/XPathReader.java @@ -0,0 +1,115 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + + + +import java.io.*; +import java.util.*; +import org.jdom.*; +import org.jdom.filter2.Filters; +import org.jdom.input.*; +import org.jdom.xpath.*; + +/** + *

XPathReader demonstrates how to + * read a Servlet 2.2 Web Archive file using XPath. + *

+ * + * @author Jason Hunter + * @version 1.0 + */ +@SuppressWarnings("javadoc") +public class XPathReader { + + public static void main(String[] args) throws IOException, JDOMException { + if (args.length != 1) { + System.err.println("Usage: java XPathReader [web.xml]"); + return; + } + String filename = args[0]; + PrintStream out = System.out; + + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(new File(filename)); + + // Print servlet information + XPathExpression servletPath = + XPathFactory.instance().compile("//servlet", Filters.element()); + List servlets = servletPath.evaluate(doc); + + out.println("This WAR has "+ servlets.size() +" registered servlets:"); + for (Element servlet : servlets) { + out.print("\t" + servlet.getChild("servlet-name") + .getTextTrim() + + " for " + servlet.getChild("servlet-class") + .getTextTrim()); + List initParams = servlet.getChildren("init-param"); + out.println(" (it has " + initParams.size() + " init params)"); + } + + // Print security role information + XPathExpression rolePath = XPathFactory.instance().compile( + "//security-role/role-name/text()", Filters.text()); + List roleNames = rolePath.evaluate(doc); + + if (roleNames.isEmpty()) { + out.println("This WAR contains no roles"); + } else { + out.println("This WAR contains " + roleNames.size() + " roles:"); + for (Text t : roleNames) { + out.println("\t" + t.getTextTrim()); + } + } + } +} diff --git a/core/samples/XSLTransform.java b/core/samples/XSLTransform.java new file mode 100644 index 0000000..ae55a25 --- /dev/null +++ b/core/samples/XSLTransform.java @@ -0,0 +1,30 @@ + + +import org.jdom.*; +import org.jdom.input.*; +import org.jdom.output.*; +import org.jdom.transform.*; + +@SuppressWarnings("javadoc") +public class XSLTransform { + + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.err.println("Usage: java XSLTransformer [some.xml] [some.xsl]"); + return; + } + + String docname = args[0]; + String sheetname = args[1]; + SAXBuilder builder = new SAXBuilder(); + Document doc = builder.build(docname); + + XSLTransformer transformer = new XSLTransformer(sheetname); + Document doc2 = transformer.transform(doc); + + Format f = Format.getPrettyFormat(); + f.setLineSeparator(LineSeparator.DOS); + XMLOutputter outp = new XMLOutputter(f); + outp.output(doc2, System.out); + } +} diff --git a/core/samples/cdata.xml b/core/samples/cdata.xml new file mode 100644 index 0000000..6c33adb --- /dev/null +++ b/core/samples/cdata.xml @@ -0,0 +1,5 @@ + + + + test < one >]]> test two + diff --git a/core/samples/contents.xml b/core/samples/contents.xml new file mode 100644 index 0000000..30fb023 --- /dev/null +++ b/core/samples/contents.xml @@ -0,0 +1,67 @@ + + + + + + + + + Java and XML + + + Introduction + + What Is It? + + + How Do I Use It? + + + Why Should I Use It? + + + What's Next? + + + + + Creating XML + An XML Document + The Header + The Content + What's Next? + + + + Parsing XML + Getting Prepared + SAX Readers + Content Handlers + Error Handlers + + A Better Way to Load a Parser + + "Gotcha!" + What's Next? + + + + + + Web Publishing Frameworks + Selecting a Framework + Installation + + Using a Publishing Framework + + XSP + Cocoon 2.0 and Beyond + What's Next? + + + diff --git a/core/samples/fibo.xml b/core/samples/fibo.xml new file mode 100644 index 0000000..9b5d0ec --- /dev/null +++ b/core/samples/fibo.xml @@ -0,0 +1,29 @@ + + + 0 + 1 + 1 + 2 + 3 + 5 + 8 + 13 + 21 + 34 + 55 + 89 + 144 + 233 + 377 + 610 + 987 + 1597 + 2584 + 4181 + 6765 + 10946 + 17711 + 28657 + 46368 + 75025 + diff --git a/core/samples/inline.xml b/core/samples/inline.xml new file mode 100644 index 0000000..32bb5c4 --- /dev/null +++ b/core/samples/inline.xml @@ -0,0 +1,16 @@ + + + + + + + + + + +]> + + + God fortsättning på det nya millenniet! + \ No newline at end of file diff --git a/core/samples/namespaces.xml b/core/samples/namespaces.xml new file mode 100644 index 0000000..f77a318 --- /dev/null +++ b/core/samples/namespaces.xml @@ -0,0 +1,18 @@ + + + + + + + <xsl:value-of select="JavaXML:Title" /> + + + + + + + \ No newline at end of file diff --git a/core/samples/sax/DataFormatFilter.java b/core/samples/sax/DataFormatFilter.java new file mode 100644 index 0000000..71b3b1c --- /dev/null +++ b/core/samples/sax/DataFormatFilter.java @@ -0,0 +1,373 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + + +/** + * Filter for data- or field-oriented XML. + * + * Code and comments adapted from DataWriter-0.2, written + * by David Megginson and released into the public domain, + * without warranty. + * + *

This filter adds indentation and newlines to field-oriented + * XML without mixed content. All added indentation and newlines + * will be passed on down the filter chain.

+ * + *

In general, all whitespace in an XML document is potentially + * significant. There is, however, a large class of XML documents + * where information is strictly fielded: each element contains either + * character data or other elements, but not both. For this special + * case, it is possible for a filter to provide automatic indentation + * and newlines. Note that this class will likely not yield appropriate + * results for document-oriented XML like XHTML pages, which mix character + * data and elements together.

+ * + *

This filter will automatically place each start tag on a new line, + * optionally indented if an indent step is provided (by default, there + * is no indentation). If an element contains other elements, the end + * tag will also appear on a new line with leading indentation. Consider, + * for example, the following code:

+ * + *
+ * DataFormatFilter df = new DataFormatFilter();
+ * df.setContentHandler(new XMLWriter());
+ *
+ * df.setIndentStep(2);
+ * df.startDocument();
+ * df.startElement("Person");
+ * df.dataElement("name", "Jane Smith");
+ * df.dataElement("date-of-birth", "1965-05-23");
+ * df.dataElement("citizenship", "US");
+ * df.endElement("Person");
+ * df.endDocument();
+ * 
+ * + *

This code will produce the following document:

+ * + *
+ * <?xml version="1.0"?>
+ *
+ * <Person>
+ *   <name>Jane Smith</name>
+ *   <date-of-birth>1965-05-23</date-of-birth>
+ *   <citizenship>US</citizenship>
+ * </Person>
+ * 
+ * + * @see DataUnformatFilter + */ +public class DataFormatFilter extends XMLFilterBase +{ + + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Create a new filter. + */ + public DataFormatFilter() + { + } + + + /** + * Create a new filter. + * + *

Use the XMLReader provided as the source of events.

+ * + * @param xmlreader The parent in the filter chain. + */ + public DataFormatFilter(XMLReader xmlreader) + { + super(xmlreader); + } + + + //////////////////////////////////////////////////////////////////// + // Accessors and setters. + //////////////////////////////////////////////////////////////////// + + + /** + * Return the current indent step. + * + *

Return the current indent step: each start tag will be + * indented by this number of spaces times the number of + * ancestors that the element has.

+ * + * @return The number of spaces in each indentation step, + * or 0 or less for no indentation. + * @see #setIndentStep + */ + public int getIndentStep () + { + return indentStep; + } + + + /** + * Set the current indent step. + * + * @param indentStep The new indent step (0 or less for no + * indentation). + * @see #getIndentStep + */ + public void setIndentStep (int indentStep) + { + this.indentStep = indentStep; + } + + + + //////////////////////////////////////////////////////////////////// + // Public methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Reset the filter so that it can be reused. + * + *

This method is especially useful if the filter failed + * with an exception the last time through.

+ */ + public void reset () + { + state = SEEN_NOTHING; + stateStack = new Stack(); + } + + + + //////////////////////////////////////////////////////////////////// + // Methods from org.xml.sax.ContentHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Filter a start document event. + * + *

Reset state and pass the event on for further processing.

+ * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startDocument + */ + @Override + public void startDocument () + throws SAXException + { + reset(); + super.startDocument(); + } + + + /** + * Add newline and indentation prior to start tag. + * + *

Each tag will begin on a new line, and will be + * indented by the current indent step times the number + * of ancestors that the element has.

+ * + *

The newline and indentation will be passed on down + * the filter chain through regular characters events.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's qualified (prefixed) name. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + @Override + public void startElement (String uri, String localName, + String qName, Attributes atts) + throws SAXException + { + if (!stateStack.empty()) { + doNewline(); + doIndent(); + } + stateStack.push(SEEN_ELEMENT); + state = SEEN_NOTHING; + super.startElement(uri, localName, qName, atts); + } + + + /** + * Add newline and indentation prior to end tag. + * + *

If the element has contained other elements, the tag + * will appear indented on a new line; otherwise, it will + * appear immediately following whatever came before.

+ * + *

The newline and indentation will be passed on down + * the filter chain through regular characters events.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's qualified (prefixed) name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + @Override + public void endElement (String uri, String localName, String qName) + throws SAXException + { + boolean seenElement = (state == SEEN_ELEMENT); + state = stateStack.pop(); + if (seenElement) { + doNewline(); + doIndent(); + } + super.endElement(uri, localName, qName); + } + + + /** + * Filter a character data event. + * + * @param ch The characters to write. + * @param start The starting position in the array. + * @param length The number of characters to use. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#characters + */ + @Override + public void characters (char ch[], int start, int length) + throws SAXException + { + state = SEEN_DATA; + super.characters(ch, start, length); + } + + + + //////////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Add newline. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + */ + private void doNewline () + throws SAXException + { + super.characters(NEWLINE, 0, NEWLINE.length); + } + + + /** + * Add indentation for the current level. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + */ + private void doIndent () + throws SAXException + { + int n = indentStep * stateStack.size(); + if (n > 0) { + char ch[] = new char[n]; + for (int i = 0; i < n; i++) { + ch[i] = INDENT_CHAR; + } + super.characters(ch, 0, n); + } + } + + + + + //////////////////////////////////////////////////////////////////// + // Constants. + //////////////////////////////////////////////////////////////////// + + private static final Object SEEN_NOTHING = new Object(); + private static final Object SEEN_ELEMENT = new Object(); + private static final Object SEEN_DATA = new Object(); + + private static final char[] NEWLINE = new char[] {'\n'}; + private static final char INDENT_CHAR = ' '; + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + private Object state = SEEN_NOTHING; + private Stack stateStack = new Stack(); + + private int indentStep = 0; + +} + +// end of DataFormatFilter.java diff --git a/core/samples/sax/DataUnformatFilter.java b/core/samples/sax/DataUnformatFilter.java new file mode 100644 index 0000000..5fc4cea --- /dev/null +++ b/core/samples/sax/DataUnformatFilter.java @@ -0,0 +1,349 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.util.Stack; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + + +/** + * Filter for removing formatting from data- or field-oriented XML. + * + * Code and comments adapted from DataWriter-0.2, written + * by David Megginson and released into the public domain, + * without warranty. + * + *

This filter removes leading and trailing whitespace from + * field-oriented XML without mixed content. Note that this class will + * likely not yield appropriate results for document-oriented XML like + * XHTML pages, which mix character data and elements together.

+ * + * @see DataFormatFilter + */ +@SuppressWarnings("javadoc") +public class DataUnformatFilter extends XMLFilterBase +{ + + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Create a new filter. + */ + public DataUnformatFilter() + { + } + + + /** + * Create a new filter. + * + *

Use the XMLReader provided as the source of events.

+ * + * @param xmlreader The parent in the filter chain. + */ + public DataUnformatFilter(XMLReader xmlreader) + { + super(xmlreader); + } + + + + //////////////////////////////////////////////////////////////////// + // Public methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Reset the filter so that it can be reused. + * + *

This method is especially useful if the filter failed + * with an exception the last time through.

+ */ + public void reset () + { + state = SEEN_NOTHING; + stateStack = new Stack(); + whitespace = new StringBuffer(); + } + + + + //////////////////////////////////////////////////////////////////// + // Methods from org.xml.sax.ContentHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Filter a start document event. + * + *

Reset state and pass the event on for further processing.

+ * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startDocument + */ + @Override + public void startDocument () + throws SAXException + { + reset(); + super.startDocument(); + } + + + /** + * Filter a start element event. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's qualified (prefixed) name. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + @Override + public void startElement (String uri, String localName, + String qName, Attributes atts) + throws SAXException + { + clearWhitespace(); + stateStack.push(SEEN_ELEMENT); + state = SEEN_NOTHING; + super.startElement(uri, localName, qName, atts); + } + + + /** + * Filter an end element event. + * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's qualified (prefixed) name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + @Override + public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (state == SEEN_ELEMENT) { + clearWhitespace(); + } else { + emitWhitespace(); + } + state = stateStack.pop(); + super.endElement(uri, localName, qName); + } + + + /** + * Filter a character data event. + * + * @param ch The characters to write. + * @param start The starting position in the array. + * @param length The number of characters to use. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#characters + */ + @Override + public void characters (char ch[], int start, int length) + throws SAXException + { + if (state != SEEN_DATA) { + + /* Look for non-whitespace. */ + + int end = start + length; + while (end-- > start) { + if (!isXMLWhitespace(ch[end])) + break; + } + + /* + * If all the characters are whitespace, save them for later. + * If we've got some data, emit any saved whitespace and update + * our state to show we've seen data. + */ + + if (end < start) { + saveWhitespace(ch, start, length); + } else { + state = SEEN_DATA; + emitWhitespace(); + } + } + + /* Pass on everything inside a data field. */ + + if (state == SEEN_DATA) { + super.characters(ch, start, length); + } + } + + + /** + * Filter an ignorable whitespace event. + * + * @param ch The array of characters to write. + * @param start The starting position in the array. + * @param length The number of characters to write. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#ignorableWhitespace + */ + @Override + public void ignorableWhitespace (char ch[], int start, int length) + throws SAXException + { + emitWhitespace(); + // ignore + } + + + /** + * Filter a processing instruction event. + * + * @param target The PI target. + * @param data The PI data. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#processingInstruction + */ + @Override + public void processingInstruction (String target, String data) + throws SAXException + { + emitWhitespace(); + super.processingInstruction(target, data); + } + + + + //////////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Saves trailing whitespace. + */ + protected void saveWhitespace (char[] ch, int start, int length) { + whitespace.append(ch, start, length); + } + + + /** + * Passes saved whitespace down the filter chain. + */ + protected void emitWhitespace () + throws SAXException + { + char[] data = new char[whitespace.length()]; + whitespace.getChars(0, data.length, data, 0); + whitespace.setLength(0); + super.characters(data, 0, data.length); + } + + + /** + * Discards saved whitespace. + */ + protected void clearWhitespace () { + whitespace.setLength(0); + } + + + /** + * Returns true if character is XML whitespace. + */ + private boolean isXMLWhitespace (char c) + { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; + } + + + + + //////////////////////////////////////////////////////////////////// + // Constants. + //////////////////////////////////////////////////////////////////// + + private static final Object SEEN_NOTHING = new Object(); + private static final Object SEEN_ELEMENT = new Object(); + private static final Object SEEN_DATA = new Object(); + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + private Object state = SEEN_NOTHING; + private Stack stateStack = new Stack(); + + private StringBuffer whitespace = new StringBuffer(); + +} + +// end of DataUnformatFilter.java diff --git a/core/samples/sax/DocumentReader.java b/core/samples/sax/DocumentReader.java new file mode 100644 index 0000000..435ee3a --- /dev/null +++ b/core/samples/sax/DocumentReader.java @@ -0,0 +1,88 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.io.IOException; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.output.SAXOutputter; + +/** + * An XMLReader wrapper for JDOM documents. + */ +@SuppressWarnings("javadoc") +public class DocumentReader extends XMLReaderBase { + + private final Document doc; + + /** Creates new DocumentReader */ + public DocumentReader(Document doc) { + this.doc = doc; + } + + @Override + public void parse(InputSource input) throws SAXException, IOException { + SAXOutputter outputter = new SAXOutputter(this, this, this, this, this); + try { + outputter.output(doc); + } + catch (JDOMException ex) { + throw new SAXException(ex); + } + } +} diff --git a/core/samples/sax/FilterTest.java b/core/samples/sax/FilterTest.java new file mode 100644 index 0000000..1abfb75 --- /dev/null +++ b/core/samples/sax/FilterTest.java @@ -0,0 +1,116 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.io.InputStream; + +import org.jdom.Document; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; + +/** + * Tests SAXBuilder's XMLFilter feature + * + * @author joe.bowbeer + */ +@SuppressWarnings("javadoc") +public class FilterTest { + + /** Creates new FilterTest */ + public FilterTest() { + } + + /** + * @param args the command line arguments + */ + public static void main (String args[]) throws Exception { + + /* XMLWriter for viewing unfiltered input. */ + + XMLWriter echo = new XMLWriter(); + + /* Add pretty formatting to unformatted xml file. */ + + SAXBuilder builder = new SAXBuilder(); + DataFormatFilter format = new DataFormatFilter(echo); + format.setIndentStep(4); + builder.setXMLFilter(format); + InputStream in = FilterTest.class.getResourceAsStream("test1.xml"); + + System.out.println(" -- test1.xml unfiltered -- \n"); + Document doc = builder.build(in); + + System.out.println(" -- test1.xml filtered by DataFormatFilter --\n"); + XMLOutputter outputter = new XMLOutputter(); + outputter.output(doc, System.out); + + System.out.println("\n"); + + /* Remove pretty formatting from formatted xml file. */ + + builder = new SAXBuilder(); + builder.setXMLFilter( new DataUnformatFilter(echo) ); + in = FilterTest.class.getResourceAsStream("test2.xml"); + + System.out.println(" -- test2.xml unfiltered --\n"); + doc = builder.build(in); + + System.out.println(" -- test2.xml filtered by DataUnformatFilter --\n"); + outputter = new XMLOutputter(); + outputter.output(doc, System.out); + + System.out.println("\n"); + } + +} diff --git a/core/samples/sax/ReaderTest.java b/core/samples/sax/ReaderTest.java new file mode 100644 index 0000000..cc401e3 --- /dev/null +++ b/core/samples/sax/ReaderTest.java @@ -0,0 +1,120 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.io.InputStream; +import java.io.StringReader; +import java.io.StringWriter; + +import org.xml.sax.InputSource; +import org.xml.sax.XMLReader; + +import org.jdom.Document; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter; + +/** + * Tests DocumentReader + * + * @author joe.bowbeer + */ +@SuppressWarnings("javadoc") +public class ReaderTest { + + /** Creates new ReaderTest */ + public ReaderTest() { + } + + /** + * @param args the command line arguments + */ + public static void main (String args[]) throws Exception { + + /* XMLWriter for viewing SAX events. */ + + XMLWriter echo = new XMLWriter(); + + /* Build document from xml file. */ + + SAXBuilder builder = new SAXBuilder(); + builder.setXMLFilter(echo); + InputStream in = FilterTest.class.getResourceAsStream("test2.xml"); + + System.out.println(" -- SAXBuilder(test2.xml), echo by XMLWriter -- \n"); + Document doc = builder.build(in); + + System.out.println(" -- DocumentReader(doc) output by XMLWriter --\n"); + XMLReader parser = new DocumentReader(doc); + echo.setParent(parser); + StringWriter writer = new StringWriter(); + parser = new XMLWriter(echo, writer); + parser.parse((InputSource)null); + + /* Reconstitute document from regurgitated string. */ + + builder = new SAXBuilder(); + builder.setXMLFilter(echo); + String xml = writer.toString(); + + System.out.println(" -- xml string--\n"); + doc = builder.build(new StringReader(xml)); + + System.out.println(" -- SAXBuilder(xml) output by XMLOutputter --\n"); + XMLOutputter outputter = new XMLOutputter(); + outputter.output(doc, System.out); + + System.out.println("\n"); + } + +} diff --git a/core/samples/sax/XMLFilterBase.java b/core/samples/sax/XMLFilterBase.java new file mode 100644 index 0000000..2643fa5 --- /dev/null +++ b/core/samples/sax/XMLFilterBase.java @@ -0,0 +1,790 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.io.IOException; + +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLFilterImpl; + +/** + * Adds convenience methods and lexical event filtering to base + * SAX2 Filter implementation. + * + * Code and comments adapted from XMLWriter-0.2, written + * by David Megginson and released into the public domain, + * without warranty. + * + *

The convenience methods are provided so that clients do not have to + * create empty attribute lists or provide empty strings as parameters; + * for example, the method invocation

+ * + *
+ * w.startElement("foo");
+ * 
+ * + *

is equivalent to the regular SAX2 ContentHandler method

+ * + *
+ * w.startElement("", "foo", "", new AttributesImpl());
+ * 
+ * + *

Except that it is more efficient because it does not allocate + * a new empty attribute list each time.

+ * + *

In fact, there is an even simpler convenience method, + * dataElement, designed for writing elements that + * contain only character data.

+ * + *
+ * w.dataElement("greeting", "Hello, world!");
+ * 
+ * + *

is equivalent to

+ * + *
+ * w.startElement("greeting");
+ * w.characters("Hello, world!");
+ * w.endElement("greeting");
+ * 
+ * + * @see org.xml.sax.helpers.XMLFilterImpl + */ +@SuppressWarnings("javadoc") +public class XMLFilterBase extends XMLFilterImpl implements LexicalHandler +{ + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Construct an XML filter with no parent. + * + *

This filter will have no parent: you must assign a parent + * before you start a parse or do any configuration with + * setFeature or setProperty.

+ * + * @see org.xml.sax.XMLReader#setFeature + * @see org.xml.sax.XMLReader#setProperty + */ + public XMLFilterBase() + { + } + + + /** + * Create an XML filter with the specified parent. + * + *

Use the XMLReader provided as the source of events.

+ * + * @param xmlreader The parent in the filter chain. + */ + public XMLFilterBase(XMLReader parent) + { + super(parent); + } + + + + //////////////////////////////////////////////////////////////////// + // Convenience methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Start a new element without a qname or attributes. + * + *

This method will provide a default empty attribute + * list and an empty string for the qualified name. It invokes + * {@link #startElement(String, String, String, Attributes)} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement (String uri, String localName) + throws SAXException + { + startElement(uri, localName, "", EMPTY_ATTS); + } + + + /** + * Start a new element without a Namespace URI or qname. + * + *

This method will provide an empty string for the + * Namespace URI, and empty string for the qualified name. + * It invokes + * {@link #startElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement (String localName, Attributes atts) + throws SAXException + { + startElement("", localName, "", atts); + } + + + /** + * Start a new element without a Namespace URI, qname, or attributes. + * + *

This method will provide an empty string for the + * Namespace URI, and empty string for the qualified name, + * and a default empty attribute list. It invokes + * {@link #startElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement (String localName) + throws SAXException + { + startElement("", localName, "", EMPTY_ATTS); + } + + + /** + * End an element without a qname. + * + *

This method will supply an empty string for the qName. + * It invokes {@link #endElement(String, String, String)} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + public void endElement (String uri, String localName) + throws SAXException + { + endElement(uri, localName, ""); + } + + + /** + * End an element without a Namespace URI or qname. + * + *

This method will supply an empty string for the qName + * and an empty string for the Namespace URI. + * It invokes {@link #endElement(String, String, String)} + * directly.

+ * + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + public void endElement (String localName) + throws SAXException + { + endElement("", localName, ""); + } + + + /** + * Add an empty element. + * + * Both a {@link #startElement startElement} and an + * {@link #endElement endElement} event will be passed on down + * the filter chain. + * + * @param uri The element's Namespace URI, or the empty string + * if the element has no Namespace or if Namespace + * processing is not being performed. + * @param localName The element's local name (without prefix). This + * parameter must be provided. + * @param qName The element's qualified name (with prefix), or + * the empty string if none is available. This parameter + * is strictly advisory: the writer may or may not use + * the prefix attached. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see org.xml.sax.ContentHandler#endElement + */ + public void emptyElement (String uri, String localName, + String qName, Attributes atts) + throws SAXException + { + startElement(uri, localName, qName, atts); + endElement(uri, localName, qName); + } + + + /** + * Add an empty element without a qname or attributes. + * + *

This method will supply an empty string for the qname + * and an empty attribute list. It invokes + * {@link #emptyElement(String, String, String, Attributes)} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see #emptyElement(String, String, String, Attributes) + */ + public void emptyElement (String uri, String localName) + throws SAXException + { + emptyElement(uri, localName, "", EMPTY_ATTS); + } + + + /** + * Add an empty element without a Namespace URI or qname. + * + *

This method will provide an empty string for the + * Namespace URI, and empty string for the qualified name. + * It invokes + * {@link #emptyElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void emptyElement (String localName, Attributes atts) + throws SAXException + { + emptyElement("", localName, "", atts); + } + + + /** + * Add an empty element without a Namespace URI, qname or attributes. + * + *

This method will supply an empty string for the qname, + * and empty string for the Namespace URI, and an empty + * attribute list. It invokes + * {@link #emptyElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see #emptyElement(String, String, String, Attributes) + */ + public void emptyElement (String localName) + throws SAXException + { + emptyElement("", localName, "", EMPTY_ATTS); + } + + + /** + * Add an element with character data content. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag.

+ * + *

This method invokes + * {@link @see org.xml.sax.ContentHandler#startElement}, + * followed by + * {@link #characters(String)}, followed by + * {@link @see org.xml.sax.ContentHandler#endElement}.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's default qualified name. + * @param atts The element's attributes. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String uri, String localName, + String qName, Attributes atts, + String content) + throws SAXException + { + startElement(uri, localName, qName, atts); + characters(content); + endElement(uri, localName, qName); + } + + + /** + * Add an element with character data content but no qname or attributes. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag. This method provides an empty string + * for the qname and an empty attribute list. It invokes + * {@link #dataElement(String, String, String, Attributes, String)}} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String uri, String localName, String content) + throws SAXException + { + dataElement(uri, localName, "", EMPTY_ATTS, content); + } + + + /** + * Add an element with character data content but no Namespace URI or qname. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag. The method provides an empty string for the + * Namespace URI, and empty string for the qualified name. It invokes + * {@link #dataElement(String, String, String, Attributes, String)}} + * directly.

+ * + * @param localName The element's local name. + * @param atts The element's attributes. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String localName, Attributes atts, String content) + throws SAXException + { + dataElement("", localName, "", atts, content); + } + + + /** + * Add an element with character data content but no attributes + * or Namespace URI. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag. The method provides an empty string for the + * Namespace URI, and empty string for the qualified name, + * and an empty attribute list. It invokes + * {@link #dataElement(String, String, String, Attributes, String)}} + * directly.

+ * + * @param localName The element's local name. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String localName, String content) + throws SAXException + { + dataElement("", localName, "", EMPTY_ATTS, content); + } + + + /** + * Add a string of character data, with XML escaping. + * + *

This is a convenience method that takes an XML + * String, converts it to a character array, then invokes + * {@link @see org.xml.sax.ContentHandler#characters}.

+ * + * @param data The character data. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see @see org.xml.sax.ContentHandler#characters + */ + public void characters (String data) + throws SAXException + { + char ch[] = data.toCharArray(); + characters(ch, 0, ch.length); + } + + + + //////////////////////////////////////////////////////////////////// + // Override org.xml.sax.helpers.XMLFilterImpl methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the value of a property. + * + *

This will always fail if the parent is null.

+ * + * @param name The property name. + * @param state The requested property value. + * @exception org.xml.sax.SAXNotRecognizedException When the + * XMLReader does not recognize the property name. + * @exception org.xml.sax.SAXNotSupportedException When the + * XMLReader recognizes the property name but + * cannot set the requested value. + * @see org.xml.sax.XMLReader#setProperty + */ + @Override + public void setProperty (String name, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException + { + for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { + if (LEXICAL_HANDLER_NAMES[i].equals(name)) { + setLexicalHandler((LexicalHandler) value); + return; + } + } + super.setProperty(name, value); + } + + + /** + * Look up the value of a property. + * + * @param name The property name. + * @return The current value of the property. + * @exception org.xml.sax.SAXNotRecognizedException When the + * XMLReader does not recognize the feature name. + * @exception org.xml.sax.SAXNotSupportedException When the + * XMLReader recognizes the property name but + * cannot determine its value at this time. + * @see org.xml.sax.XMLReader#setFeature + */ + @Override + public Object getProperty (String name) + throws SAXNotRecognizedException, SAXNotSupportedException + { + for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { + if (LEXICAL_HANDLER_NAMES[i].equals(name)) { + return getLexicalHandler(); + } + } + return super.getProperty(name); + } + + + /** + * Parse a document. + * + * @param input The input source for the document entity. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @exception java.io.IOException An IO exception from the parser, + * possibly from a byte stream or character stream + * supplied by the application. + * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource) + */ + @Override + public void parse (InputSource input) + throws SAXException, IOException + { + installLexicalHandler(); + super.parse(input); + } + + + + //////////////////////////////////////////////////////////////////// + // Registration of org.xml.sax.ext.LexicalHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the lexical handler. + * + * @param handler The new lexical handler. + * @exception java.lang.NullPointerException If the handler + * is null. + */ + public void setLexicalHandler (LexicalHandler handler) + { + if (handler == null) { + throw new NullPointerException("Null lexical handler"); + } + lexicalHandler = handler; + } + + + /** + * Get the current lexical handler. + * + * @return The current lexical handler, or null if none was set. + */ + public LexicalHandler getLexicalHandler () + { + return lexicalHandler; + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.ext.LexicalHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Filter a start DTD event. + * + * @param name The document type name. + * @param publicId The declared public identifier for the + * external DTD subset, or null if none was declared. + * @param systemId The declared system identifier for the + * external DTD subset, or null if none was declared. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startDTD + */ + @Override + public void startDTD(String name, String publicId, String systemId) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startDTD(name, publicId, systemId); + } + } + + + /** + * Filter a end DTD event. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endDTD + */ + @Override + public void endDTD() + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endDTD(); + } + } + + + /* + * Filter a start entity event. + * + * @param name The name of the entity. If it is a parameter + * entity, the name will begin with '%', and if it is the + * external DTD subset, it will be "[dtd]". + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startEntity + */ + @Override + public void startEntity(String name) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startEntity(name); + } + } + + + /* + * Filter a end entity event. + * + * @param name The name of the entity that is ending. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endEntity + */ + @Override + public void endEntity(String name) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endEntity(name); + } + } + + + /* + * Filter a start CDATA event. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startCDATA + */ + @Override + public void startCDATA() + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startCDATA(); + } + } + + + /* + * Filter a end CDATA event. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endCDATA + */ + @Override + public void endCDATA() + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endCDATA(); + } + } + + + /* + * Filter a comment event. + * + * @param ch An array holding the characters in the comment. + * @param start The starting position in the array. + * @param length The number of characters to use from the array. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#comment + */ + @Override + public void comment(char[] ch, int start, int length) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.comment(ch, start, length); + } + } + + + + //////////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Installs lexical handler before a parse. + * + *

Before every parse, check whether the parent is + * non-null, and re-register the filter for the lexical + * events.

+ */ + private void installLexicalHandler () + { + XMLReader parent = getParent(); + if (parent == null) { + throw new NullPointerException("No parent for filter"); + } + // try to register for lexical events + for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { + try { + parent.setProperty(LEXICAL_HANDLER_NAMES[i], this); + break; + } + catch (SAXNotRecognizedException ex) { + // ignore + } + catch (SAXNotSupportedException ex) { + // ignore + } + } + } + + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + + private LexicalHandler lexicalHandler = null; + + + + //////////////////////////////////////////////////////////////////// + // Constants. + //////////////////////////////////////////////////////////////////// + + + protected static final Attributes EMPTY_ATTS = new AttributesImpl(); + + protected static final String[] LEXICAL_HANDLER_NAMES = { + "http://xml.org/sax/properties/lexical-handler", + "http://xml.org/sax/handlers/LexicalHandler" + }; + + +} + +// end of XMLFilterBase.java diff --git a/core/samples/sax/XMLReaderBase.java b/core/samples/sax/XMLReaderBase.java new file mode 100644 index 0000000..e3ea6f0 --- /dev/null +++ b/core/samples/sax/XMLReaderBase.java @@ -0,0 +1,1250 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.io.IOException; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; + + +/** + * Base class for implementing an XML reader. + * + * Adapted from David Megginson's XMLFilterImpl and XMLFilterBase. + */ +@SuppressWarnings("javadoc") +public abstract class XMLReaderBase extends DefaultHandler +implements LexicalHandler, XMLReader +{ + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Creates new XMLReaderBase. + */ + public XMLReaderBase () + { + } + + + //////////////////////////////////////////////////////////////////// + // Convenience methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Start a new element without a qname or attributes. + * + *

This method will provide a default empty attribute + * list and an empty string for the qualified name. It invokes + * {@link #startElement(String, String, String, Attributes)} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement (String uri, String localName) + throws SAXException + { + startElement(uri, localName, "", EMPTY_ATTS); + } + + + /** + * Start a new element without a Namespace URI or qname. + * + *

This method will provide an empty string for the + * Namespace URI, and empty string for the qualified name. + * It invokes + * {@link #startElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement (String localName, Attributes atts) + throws SAXException + { + startElement("", localName, "", atts); + } + + + /** + * Start a new element without a Namespace URI, qname, or attributes. + * + *

This method will provide an empty string for the + * Namespace URI, and empty string for the qualified name, + * and a default empty attribute list. It invokes + * {@link #startElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void startElement (String localName) + throws SAXException + { + startElement("", localName, "", EMPTY_ATTS); + } + + + /** + * End an element without a qname. + * + *

This method will supply an empty string for the qName. + * It invokes {@link #endElement(String, String, String)} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + public void endElement (String uri, String localName) + throws SAXException + { + endElement(uri, localName, ""); + } + + + /** + * End an element without a Namespace URI or qname. + * + *

This method will supply an empty string for the qName + * and an empty string for the Namespace URI. + * It invokes {@link #endElement(String, String, String)} + * directly.

+ * + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + public void endElement (String localName) + throws SAXException + { + endElement("", localName, ""); + } + + + /** + * Add an empty element. + * + * Both a {@link #startElement startElement} and an + * {@link #endElement endElement} event will be passed on down + * the filter chain. + * + * @param uri The element's Namespace URI, or the empty string + * if the element has no Namespace or if Namespace + * processing is not being performed. + * @param localName The element's local name (without prefix). This + * parameter must be provided. + * @param qName The element's qualified name (with prefix), or + * the empty string if none is available. This parameter + * is strictly advisory: the writer may or may not use + * the prefix attached. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see org.xml.sax.ContentHandler#endElement + */ + public void emptyElement (String uri, String localName, + String qName, Attributes atts) + throws SAXException + { + startElement(uri, localName, qName, atts); + endElement(uri, localName, qName); + } + + + /** + * Add an empty element without a qname or attributes. + * + *

This method will supply an empty string for the qname + * and an empty attribute list. It invokes + * {@link #emptyElement(String, String, String, Attributes)} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see #emptyElement(String, String, String, Attributes) + */ + public void emptyElement (String uri, String localName) + throws SAXException + { + emptyElement(uri, localName, "", EMPTY_ATTS); + } + + + /** + * Add an empty element without a Namespace URI or qname. + * + *

This method will provide an empty string for the + * Namespace URI, and empty string for the qualified name. + * It invokes + * {@link #emptyElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @param atts The element's attribute list. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + public void emptyElement (String localName, Attributes atts) + throws SAXException + { + emptyElement("", localName, "", atts); + } + + + /** + * Add an empty element without a Namespace URI, qname or attributes. + * + *

This method will supply an empty string for the qname, + * and empty string for the Namespace URI, and an empty + * attribute list. It invokes + * {@link #emptyElement(String, String, String, Attributes)} + * directly.

+ * + * @param localName The element's local name. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see #emptyElement(String, String, String, Attributes) + */ + public void emptyElement (String localName) + throws SAXException + { + emptyElement("", localName, "", EMPTY_ATTS); + } + + + /** + * Add an element with character data content. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag.

+ * + *

This method invokes + * {@link @see org.xml.sax.ContentHandler#startElement}, + * followed by + * {@link #characters(String)}, followed by + * {@link @see org.xml.sax.ContentHandler#endElement}.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param qName The element's default qualified name. + * @param atts The element's attributes. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String uri, String localName, + String qName, Attributes atts, + String content) + throws SAXException + { + startElement(uri, localName, qName, atts); + characters(content); + endElement(uri, localName, qName); + } + + + /** + * Add an element with character data content but no qname or attributes. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag. This method provides an empty string + * for the qname and an empty attribute list. It invokes + * {@link #dataElement(String, String, String, Attributes, String)}} + * directly.

+ * + * @param uri The element's Namespace URI. + * @param localName The element's local name. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String uri, String localName, String content) + throws SAXException + { + dataElement(uri, localName, "", EMPTY_ATTS, content); + } + + + /** + * Add an element with character data content but no Namespace URI or qname. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag. The method provides an empty string for the + * Namespace URI, and empty string for the qualified name. It invokes + * {@link #dataElement(String, String, String, Attributes, String)}} + * directly.

+ * + * @param localName The element's local name. + * @param atts The element's attributes. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String localName, Attributes atts, String content) + throws SAXException + { + dataElement("", localName, "", atts, content); + } + + + /** + * Add an element with character data content but no attributes + * or Namespace URI. + * + *

This is a convenience method to add a complete element + * with character data content, including the start tag + * and end tag. The method provides an empty string for the + * Namespace URI, and empty string for the qualified name, + * and an empty attribute list. It invokes + * {@link #dataElement(String, String, String, Attributes, String)}} + * directly.

+ * + * @param localName The element's local name. + * @param content The character data content. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + * @see #characters(String) + * @see org.xml.sax.ContentHandler#endElement + */ + public void dataElement (String localName, String content) + throws SAXException + { + dataElement("", localName, "", EMPTY_ATTS, content); + } + + + /** + * Add a string of character data, with XML escaping. + * + *

This is a convenience method that takes an XML + * String, converts it to a character array, then invokes + * {@link @see org.xml.sax.ContentHandler#characters}.

+ * + * @param data The character data. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see @see org.xml.sax.ContentHandler#characters + */ + public void characters (String data) + throws SAXException + { + char ch[] = data.toCharArray(); + characters(ch, 0, ch.length); + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.XMLReader. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the state of a feature. + * + *

This will always fail.

+ * + * @param name The feature name. + * @param state The requested feature state. + * @exception org.xml.sax.SAXNotRecognizedException When the + * XMLReader does not recognize the feature name. + * @exception org.xml.sax.SAXNotSupportedException When the + * XMLReader recognizes the feature name but + * cannot set the requested value. + * @see org.xml.sax.XMLReader#setFeature + */ + @Override + public void setFeature (String name, boolean state) + throws SAXNotRecognizedException, SAXNotSupportedException + { + throw new SAXNotRecognizedException("Feature: " + name); + } + + + /** + * Look up the state of a feature. + * + *

This will always fail.

+ * + * @param name The feature name. + * @return The current state of the feature. + * @exception org.xml.sax.SAXNotRecognizedException When the + * XMLReader does not recognize the feature name. + * @exception org.xml.sax.SAXNotSupportedException When the + * XMLReader recognizes the feature name but + * cannot determine its state at this time. + * @see org.xml.sax.XMLReader#getFeature + */ + @Override + public boolean getFeature (String name) + throws SAXNotRecognizedException, SAXNotSupportedException + { + throw new SAXNotRecognizedException("Feature: " + name); + } + + + /** + * Set the value of a property. + * + *

Only lexical-handler properties are recognized.

+ * + * @param name The property name. + * @param state The requested property value. + * @exception org.xml.sax.SAXNotRecognizedException When the + * XMLReader does not recognize the property name. + * @exception org.xml.sax.SAXNotSupportedException When the + * XMLReader recognizes the property name but + * cannot set the requested value. + * @see org.xml.sax.XMLReader#setProperty + */ + @Override + public void setProperty (String name, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException + { + for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { + if (LEXICAL_HANDLER_NAMES[i].equals(name)) { + setLexicalHandler((LexicalHandler) value); + return; + } + } + throw new SAXNotRecognizedException("Property: " + name); + } + + + /** + * Look up the value of a property. + * + *

Only lexical-handler properties are recognized.

+ * + * @param name The property name. + * @return The current value of the property. + * @exception org.xml.sax.SAXNotRecognizedException When the + * XMLReader does not recognize the feature name. + * @exception org.xml.sax.SAXNotSupportedException When the + * XMLReader recognizes the property name but + * cannot determine its value at this time. + * @see org.xml.sax.XMLReader#setFeature + */ + @Override + public Object getProperty (String name) + throws SAXNotRecognizedException, SAXNotSupportedException + { + for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) { + if (LEXICAL_HANDLER_NAMES[i].equals(name)) { + return getLexicalHandler(); + } + } + throw new SAXNotRecognizedException("Property: " + name); + } + + + /** + * Parse a document. Subclass must implement. + * + * @param input The input source for the document entity. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @exception java.io.IOException An IO exception from the parser, + * possibly from a byte stream or character stream + * supplied by the application. + * @see org.xml.sax.XMLReader#parse(org.xml.sax.InputSource) + */ + @Override + public abstract void parse (InputSource input) + throws SAXException, IOException; + + + /** + * Parse a document. + * + * @param systemId The system identifier as a fully-qualified URI. + * @exception org.xml.sax.SAXException Any SAX exception, possibly + * wrapping another exception. + * @exception java.io.IOException An IO exception from the parser, + * possibly from a byte stream or character stream + * supplied by the application. + * @see org.xml.sax.XMLReader#parse(java.lang.String) + */ + @Override + public void parse (String systemId) + throws SAXException, IOException + { + parse(new InputSource(systemId)); + } + + + /** + * Set the entity resolver. + * + * @param resolver The new entity resolver. + * @exception java.lang.NullPointerException If the resolver + * is null. + * @see org.xml.sax.XMLReader#setEntityResolver + */ + @Override + public void setEntityResolver (EntityResolver resolver) + { + if (resolver == null) { + throw new NullPointerException("Null entity resolver"); + } + entityResolver = resolver; + } + + + /** + * Get the current entity resolver. + * + * @return The current entity resolver, or null if none was set. + * @see org.xml.sax.XMLReader#getEntityResolver + */ + @Override + public EntityResolver getEntityResolver () + { + return entityResolver; + } + + + /** + * Set the DTD event handler. + * + * @param resolver The new DTD handler. + * @exception java.lang.NullPointerException If the handler + * is null. + * @see org.xml.sax.XMLReader#setDTDHandler + */ + @Override + public void setDTDHandler (DTDHandler handler) + { + if (handler == null) { + throw new NullPointerException("Null DTD handler"); + } + dtdHandler = handler; + } + + + /** + * Get the current DTD event handler. + * + * @return The current DTD handler, or null if none was set. + * @see org.xml.sax.XMLReader#getDTDHandler + */ + @Override + public DTDHandler getDTDHandler () + { + return dtdHandler; + } + + + /** + * Set the content event handler. + * + * @param resolver The new content handler. + * @exception java.lang.NullPointerException If the handler + * is null. + * @see org.xml.sax.XMLReader#setContentHandler + */ + @Override + public void setContentHandler (ContentHandler handler) + { + if (handler == null) { + throw new NullPointerException("Null content handler"); + } + contentHandler = handler; + } + + + /** + * Get the content event handler. + * + * @return The current content handler, or null if none was set. + * @see org.xml.sax.XMLReader#getContentHandler + */ + @Override + public ContentHandler getContentHandler () + { + return contentHandler; + } + + + /** + * Set the error event handler. + * + * @param handle The new error handler. + * @exception java.lang.NullPointerException If the handler + * is null. + * @see org.xml.sax.XMLReader#setErrorHandler + */ + @Override + public void setErrorHandler (ErrorHandler handler) + { + if (handler == null) { + throw new NullPointerException("Null error handler"); + } + errorHandler = handler; + } + + + /** + * Get the current error event handler. + * + * @return The current error handler, or null if none was set. + * @see org.xml.sax.XMLReader#getErrorHandler + */ + @Override + public ErrorHandler getErrorHandler () + { + return errorHandler; + } + + + + //////////////////////////////////////////////////////////////////// + // Registration of org.xml.sax.ext.LexicalHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Set the lexical handler. + * + * @param handler The new lexical handler. + * @exception java.lang.NullPointerException If the handler + * is null. + */ + public void setLexicalHandler (LexicalHandler handler) + { + if (handler == null) { + throw new NullPointerException("Null lexical handler"); + } + lexicalHandler = handler; + } + + + /** + * Get the current lexical handler. + * + * @return The current lexical handler, or null if none was set. + */ + public LexicalHandler getLexicalHandler () + { + return lexicalHandler; + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.EntityResolver. + //////////////////////////////////////////////////////////////////// + + + /** + * Resolves an external entity. + * + * @param publicId The entity's public identifier, or null. + * @param systemId The entity's system identifier. + * @return A new InputSource or null for the default. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @exception java.io.IOException The client may throw an + * I/O-related exception while obtaining the + * new InputSource. + * @see org.xml.sax.EntityResolver#resolveEntity + */ + @Override + public InputSource resolveEntity (String publicId, String systemId) + throws SAXException /* IOException added in SAX2.01 bugfix release */ + { + if (entityResolver != null) { + try { + return entityResolver.resolveEntity(publicId, systemId); + } + catch (IOException ex) { + throw new SAXException(ex); + } + } + return null; + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.DTDHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Add notation declaration. + * + * @param name The notation name. + * @param publicId The notation's public identifier, or null. + * @param systemId The notation's system identifier, or null. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.DTDHandler#notationDecl + */ + @Override + public void notationDecl (String name, String publicId, String systemId) + throws SAXException + { + if (dtdHandler != null) { + dtdHandler.notationDecl(name, publicId, systemId); + } + } + + + /** + * Add unparsed entity declaration. + * + * @param name The entity name. + * @param publicId The entity's public identifier, or null. + * @param systemId The entity's system identifier, or null. + * @param notationName The name of the associated notation. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.DTDHandler#unparsedEntityDecl + */ + @Override + public void unparsedEntityDecl (String name, String publicId, + String systemId, String notationName) + throws SAXException + { + if (dtdHandler != null) { + dtdHandler.unparsedEntityDecl(name, publicId, systemId, + notationName); + } + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.ContentHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Assigns the document locator. + * + * @param locator The document locator. + * @see org.xml.sax.ContentHandler#setDocumentLocator + */ + @Override + public void setDocumentLocator (Locator locator) + { + //this.locator = locator; + if (contentHandler != null) { + contentHandler.setDocumentLocator(locator); + } + } + + + /** + * Send start of document. + * + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#startDocument + */ + @Override + public void startDocument () + throws SAXException + { + if (contentHandler != null) { + contentHandler.startDocument(); + } + } + + + /** + * Send end of document. + * + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#endDocument + */ + @Override + public void endDocument () + throws SAXException + { + if (contentHandler != null) { + contentHandler.endDocument(); + } + } + + + /** + * Sends start of namespace prefix mapping. + * + * @param prefix The Namespace prefix. + * @param uri The Namespace URI. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#startPrefixMapping + */ + @Override + public void startPrefixMapping (String prefix, String uri) + throws SAXException + { + if (contentHandler != null) { + contentHandler.startPrefixMapping(prefix, uri); + } + } + + + /** + * Sends end of namespace prefix mapping. + * + * @param prefix The Namespace prefix. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#endPrefixMapping + */ + @Override + public void endPrefixMapping (String prefix) + throws SAXException + { + if (contentHandler != null) { + contentHandler.endPrefixMapping(prefix); + } + } + + + /** + * Sends start of element. + * + * @param uri The element's Namespace URI, or the empty string. + * @param localName The element's local name, or the empty string. + * @param qName The element's qualified (prefixed) name, or the empty + * string. + * @param atts The element's attributes. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#startElement + */ + @Override + public void startElement (String uri, String localName, String qName, + Attributes atts) + throws SAXException + { + if (contentHandler != null) { + contentHandler.startElement(uri, localName, qName, atts); + } + } + + + /** + * Sends end of element. + * + * @param uri The element's Namespace URI, or the empty string. + * @param localName The element's local name, or the empty string. + * @param qName The element's qualified (prefixed) name, or the empty + * string. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#endElement + */ + @Override + public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (contentHandler != null) { + contentHandler.endElement(uri, localName, qName); + } + } + + + /** + * Sends character data. + * + * @param ch An array of characters. + * @param start The starting position in the array. + * @param length The number of characters to use from the array. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#characters + */ + @Override + public void characters (char ch[], int start, int length) + throws SAXException + { + if (contentHandler != null) { + contentHandler.characters(ch, start, length); + } + } + + + /** + * Sends ignorable whitespace. + * + * @param ch An array of characters. + * @param start The starting position in the array. + * @param length The number of characters to use from the array. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#ignorableWhitespace + */ + @Override + public void ignorableWhitespace (char ch[], int start, int length) + throws SAXException + { + if (contentHandler != null) { + contentHandler.ignorableWhitespace(ch, start, length); + } + } + + + /** + * Sends processing instruction. + * + * @param target The processing instruction target. + * @param data The text following the target. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#processingInstruction + */ + @Override + public void processingInstruction (String target, String data) + throws SAXException + { + if (contentHandler != null) { + contentHandler.processingInstruction(target, data); + } + } + + + /** + * Sends skipped entity. + * + * @param name The name of the skipped entity. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ContentHandler#skippedEntity + */ + @Override + public void skippedEntity (String name) + throws SAXException + { + if (contentHandler != null) { + contentHandler.skippedEntity(name); + } + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.ErrorHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Sends warning. + * + * @param e The nwarning as an exception. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ErrorHandler#warning + */ + @Override + public void warning (SAXParseException e) + throws SAXException + { + if (errorHandler != null) { + errorHandler.warning(e); + } + } + + + /** + * Sends error. + * + * @param e The error as an exception. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ErrorHandler#error + */ + @Override + public void error (SAXParseException e) + throws SAXException + { + if (errorHandler != null) { + errorHandler.error(e); + } + } + + + /** + * Sends fatal error. + * + * @param e The error as an exception. + * @exception org.xml.sax.SAXException The client may throw + * an exception during processing. + * @see org.xml.sax.ErrorHandler#fatalError + */ + @Override + public void fatalError (SAXParseException e) + throws SAXException + { + if (errorHandler != null) { + errorHandler.fatalError(e); + } + } + + + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.ext.LexicalHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Sends start of DTD. + * + * @param name The document type name. + * @param publicId The declared public identifier for the + * external DTD subset, or null if none was declared. + * @param systemId The declared system identifier for the + * external DTD subset, or null if none was declared. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startDTD + */ + @Override + public void startDTD(String name, String publicId, String systemId) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startDTD(name, publicId, systemId); + } + } + + + /** + * Sends end of DTD. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endDTD + */ + @Override + public void endDTD() + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endDTD(); + } + } + + + /* + * Sends start of entity. + * + * @param name The name of the entity. If it is a parameter + * entity, the name will begin with '%', and if it is the + * external DTD subset, it will be "[dtd]". + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startEntity + */ + @Override + public void startEntity(String name) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startEntity(name); + } + } + + + /* + * Sends end of entity. + * + * @param name The name of the entity that is ending. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endEntity + */ + @Override + public void endEntity(String name) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endEntity(name); + } + } + + + /* + * Sends start of CDATA. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startCDATA + */ + @Override + public void startCDATA() + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.startCDATA(); + } + } + + + /* + * Sends end of CDATA. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endCDATA + */ + @Override + public void endCDATA() + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.endCDATA(); + } + } + + + /* + * Sends comment. + * + * @param ch An array holding the characters in the comment. + * @param start The starting position in the array. + * @param length The number of characters to use from the array. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#comment + */ + @Override + public void comment(char[] ch, int start, int length) + throws SAXException { + if (lexicalHandler != null) { + lexicalHandler.comment(ch, start, length); + } + } + + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + //private Locator locator = null; + private EntityResolver entityResolver = null; + private DTDHandler dtdHandler = null; + private ContentHandler contentHandler = null; + private ErrorHandler errorHandler = null; + private LexicalHandler lexicalHandler = null; + + + + //////////////////////////////////////////////////////////////////// + // Constants. + //////////////////////////////////////////////////////////////////// + + + protected static final Attributes EMPTY_ATTS = new AttributesImpl(); + + protected static final String[] LEXICAL_HANDLER_NAMES = { + "http://xml.org/sax/properties/lexical-handler", + "http://xml.org/sax/handlers/LexicalHandler" + }; + + +} + +// end of XMLReaderBase.java diff --git a/core/samples/sax/XMLWriter.java b/core/samples/sax/XMLWriter.java new file mode 100644 index 0000000..822a85c --- /dev/null +++ b/core/samples/sax/XMLWriter.java @@ -0,0 +1,1170 @@ +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package sax; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.NamespaceSupport; + + +/** + * Filter to write an XML document from a SAX event stream. + * + * Code and comments adapted from XMLWriter-0.2, written + * by David Megginson and released into the public domain, + * without warranty. + * + *

This class can be used by itself or as part of a SAX event + * stream: it takes as input a series of SAX2 ContentHandler + * events and uses the information in those events to write + * an XML document. Since this class is a filter, it can also + * pass the events on down a filter chain for further processing + * (you can use the XMLWriter to take a snapshot of the current + * state at any point in a filter chain), and it can be + * used directly as a ContentHandler for a SAX2 XMLReader.

+ * + *

The client creates a document by invoking the methods for + * standard SAX2 events, always beginning with the + * {@link #startDocument startDocument} method and ending with + * the {@link #endDocument endDocument} method.

+ * + *

The following code will send a simple XML document to + * standard output:

+ * + *
+ * XMLWriter w = new XMLWriter();
+ *
+ * w.startDocument();
+ * w.dataElement("greeting", "Hello, world!");
+ * w.endDocument();
+ * 
+ * + *

The resulting document will look like this:

+ * + *
+ * <?xml version="1.0"?>
+ *
+ * <greeting>Hello, world!</greeting>
+ * 
+ * + *

Whitespace

+ * + *

According to the XML Recommendation, all whitespace + * in an XML document is potentially significant to an application, + * so this class never adds newlines or indentation. If you + * insert three elements in a row, as in

+ * + *
+ * w.dataElement("item", "1");
+ * w.dataElement("item", "2");
+ * w.dataElement("item", "3");
+ * 
+ * + *

you will end up with

+ * + *
+ * <item>1</item><item>3</item><item>3</item>
+ * 
+ * + *

You need to invoke one of the characters methods + * explicitly to add newlines or indentation. Alternatively, you + * can use {@link samples.sax.DataFormatFilter DataFormatFilter} + * add linebreaks and indentation (but does not support mixed content + * properly).

+ * + * + *

Namespace Support

+ * + *

The writer contains extensive support for XML Namespaces, so that + * a client application does not have to keep track of prefixes and + * supply xmlns attributes. By default, the XML writer will + * generate Namespace declarations in the form _NS1, _NS2, etc., wherever + * they are needed, as in the following example:

+ * + *
+ * w.startDocument();
+ * w.emptyElement("http://www.foo.com/ns/", "foo");
+ * w.endDocument();
+ * 
+ * + *

The resulting document will look like this:

+ * + *
+ * <?xml version="1.0"?>
+ *
+ * <_NS1:foo xmlns:_NS1="http://www.foo.com/ns/"/>
+ * 
+ * + *

In many cases, document authors will prefer to choose their + * own prefixes rather than using the (ugly) default names. The + * XML writer allows two methods for selecting prefixes:

+ * + *
    + *
  1. the qualified name
  2. + *
  3. the {@link #setPrefix setPrefix} method.
  4. + *
+ * + *

Whenever the XML writer finds a new Namespace URI, it checks + * to see if a qualified (prefixed) name is also available; if so + * it attempts to use the name's prefix (as long as the prefix is + * not already in use for another Namespace URI).

+ * + *

Before writing a document, the client can also pre-map a prefix + * to a Namespace URI with the setPrefix method:

+ * + *
+ * w.setPrefix("http://www.foo.com/ns/", "foo");
+ * w.startDocument();
+ * w.emptyElement("http://www.foo.com/ns/", "foo");
+ * w.endDocument();
+ * 
+ * + *

The resulting document will look like this:

+ * + *
+ * <?xml version="1.0"?>
+ *
+ * <foo:foo xmlns:foo="http://www.foo.com/ns/"/>
+ * 
+ * + *

The default Namespace simply uses an empty string as the prefix:

+ * + *
+ * w.setPrefix("http://www.foo.com/ns/", "");
+ * w.startDocument();
+ * w.emptyElement("http://www.foo.com/ns/", "foo");
+ * w.endDocument();
+ * 
+ * + *

The resulting document will look like this:

+ * + *
+ * <?xml version="1.0"?>
+ *
+ * <foo xmlns="http://www.foo.com/ns/"/>
+ * 
+ * + *

By default, the XML writer will not declare a Namespace until + * it is actually used. Sometimes, this approach will create + * a large number of Namespace declarations, as in the following + * example:

+ * + *
+ * <xml version="1.0"?>
+ *
+ * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ *  <rdf:Description about="http://www.foo.com/ids/books/12345">
+ *   <dc:title xmlns:dc="http://www.purl.org/dc/">A Dark Night</dc:title>
+ *   <dc:creator xmlns:dc="http://www.purl.org/dc/">Jane Smith</dc:title>
+ *   <dc:date xmlns:dc="http://www.purl.org/dc/">2000-09-09</dc:title>
+ *  </rdf:Description>
+ * </rdf:RDF>
+ * 
+ * + *

The "rdf" prefix is declared only once, because the RDF Namespace + * is used by the root element and can be inherited by all of its + * descendants; the "dc" prefix, on the other hand, is declared three + * times, because no higher element uses the Namespace. To solve this + * problem, you can instruct the XML writer to predeclare Namespaces + * on the root element even if they are not used there:

+ * + *
+ * w.forceNSDecl("http://www.purl.org/dc/");
+ * 
+ * + *

Now, the "dc" prefix will be declared on the root element even + * though it's not needed there, and can be inherited by its + * descendants:

+ * + *
+ * <xml version="1.0"?>
+ *
+ * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ *             xmlns:dc="http://www.purl.org/dc/">
+ *  <rdf:Description about="http://www.foo.com/ids/books/12345">
+ *   <dc:title>A Dark Night</dc:title>
+ *   <dc:creator>Jane Smith</dc:title>
+ *   <dc:date>2000-09-09</dc:title>
+ *  </rdf:Description>
+ * </rdf:RDF>
+ * 
+ * + *

This approach is also useful for declaring Namespace prefixes + * that be used by qualified names appearing in attribute values or + * character data.

+ * + * @see XMLFilterBase + */ +@SuppressWarnings("javadoc") +public class XMLWriter extends XMLFilterBase +{ + + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + + + /** + * Create a new XML writer. + * + *

Write to standard output.

+ */ + public XMLWriter() + { + init(null); + } + + + /** + * Create a new XML writer. + * + *

Write to the writer provided.

+ * + * @param writer The output destination, or null to use standard + * output. + */ + public XMLWriter(Writer writer) + { + init(writer); + } + + + /** + * Create a new XML writer. + * + *

Use the specified XML reader as the parent.

+ * + * @param xmlreader The parent in the filter chain, or null + * for no parent. + */ + public XMLWriter(XMLReader xmlreader) + { + super(xmlreader); + init(null); + } + + + /** + * Create a new XML writer. + * + *

Use the specified XML reader as the parent, and write + * to the specified writer.

+ * + * @param xmlreader The parent in the filter chain, or null + * for no parent. + * @param writer The output destination, or null to use standard + * output. + */ + public XMLWriter(XMLReader xmlreader, Writer writer) + { + super(xmlreader); + init(writer); + } + + + /** + * Internal initialization method. + * + *

All of the public constructors invoke this method. + * + * @param writer The output destination, or null to use + * standard output. + */ + private void init (Writer writer) + { + setOutput(writer); + nsSupport = new NamespaceSupport(); + prefixTable = new HashMap(); + forcedDeclTable = new HashMap(); + doneDeclTable = new HashMap(); + } + + + + //////////////////////////////////////////////////////////////////// + // Public methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Reset the writer. + * + *

This method is especially useful if the writer throws an + * exception before it is finished, and you want to reuse the + * writer for a new document. It is usually a good idea to + * invoke {@link #flush flush} before resetting the writer, + * to make sure that no output is lost.

+ * + *

This method is invoked automatically by the + * {@link #startDocument startDocument} method before writing + * a new document.

+ * + *

Note: this method will not + * clear the prefix or URI information in the writer or + * the selected output writer.

+ * + * @see #flush + */ + public void reset () + { + openElement = false; + elementLevel = 0; + prefixCounter = 0; + nsSupport.reset(); + inDTD = false; + } + + + /** + * Flush the output. + * + *

This method flushes the output stream. It is especially useful + * when you need to make certain that the entire document has + * been written to output but do not want to close the output + * stream.

+ * + *

This method is invoked automatically by the + * {@link #endDocument endDocument} method after writing a + * document.

+ * + * @see #reset + */ + public void flush () + throws IOException + { + output.flush(); + } + + + /** + * Set a new output destination for the document. + * + * @param writer The output destination, or null to use + * standard output. + * @return The current output writer. + * @see #flush + */ + public void setOutput (Writer writer) + { + if (writer == null) { + output = new OutputStreamWriter(System.out); + } else { + output = writer; + } + } + + + /** + * Specify a preferred prefix for a Namespace URI. + * + *

Note that this method does not actually force the Namespace + * to be declared; to do that, use the {@link + * #forceNSDecl(java.lang.String) forceNSDecl} method as well.

+ * + * @param uri The Namespace URI. + * @param prefix The preferred prefix, or "" to select + * the default Namespace. + * @see #getPrefix + * @see #forceNSDecl(java.lang.String) + * @see #forceNSDecl(java.lang.String,java.lang.String) + */ + public void setPrefix (String uri, String prefix) + { + prefixTable.put(uri, prefix); + } + + + /** + * Get the current or preferred prefix for a Namespace URI. + * + * @param uri The Namespace URI. + * @return The preferred prefix, or "" for the default Namespace. + * @see #setPrefix + */ + public String getPrefix (String uri) + { + return prefixTable.get(uri); + } + + + /** + * Force a Namespace to be declared on the root element. + * + *

By default, the XMLWriter will declare only the Namespaces + * needed for an element; as a result, a Namespace may be + * declared many places in a document if it is not used on the + * root element.

+ * + *

This method forces a Namespace to be declared on the root + * element even if it is not used there, and reduces the number + * of xmlns attributes in the document.

+ * + * @param uri The Namespace URI to declare. + * @see #forceNSDecl(java.lang.String,java.lang.String) + * @see #setPrefix + */ + public void forceNSDecl (String uri) + { + forcedDeclTable.put(uri, Boolean.TRUE); + } + + + /** + * Force a Namespace declaration with a preferred prefix. + * + *

This is a convenience method that invokes {@link + * #setPrefix setPrefix} then {@link #forceNSDecl(java.lang.String) + * forceNSDecl}.

+ * + * @param uri The Namespace URI to declare on the root element. + * @param prefix The preferred prefix for the Namespace, or "" + * for the default Namespace. + * @see #setPrefix + * @see #forceNSDecl(java.lang.String) + */ + public void forceNSDecl (String uri, String prefix) + { + setPrefix(uri, prefix); + forceNSDecl(uri); + } + + + + //////////////////////////////////////////////////////////////////// + // Methods from org.xml.sax.ContentHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Write the XML declaration at the beginning of the document. + * + * Pass the event on down the filter chain for further processing. + * + * @exception org.xml.sax.SAXException If there is an error + * writing the XML declaration, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#startDocument + */ + @Override + public void startDocument () + throws SAXException + { + reset(); + //write("\n\n"); + write("\n\n"); + super.startDocument(); + } + + + /** + * Write a newline at the end of the document. + * + * Pass the event on down the filter chain for further processing. + * + * @exception org.xml.sax.SAXException If there is an error + * writing the newline, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#endDocument + */ + @Override + public void endDocument () + throws SAXException + { + closeElement(); + write('\n'); + super.endDocument(); + try { + flush(); + } catch (IOException e) { + throw new SAXException(e); + } + } + + + /** + * Write a start tag. + * + * Pass the event on down the filter chain for further processing. + * + * @param uri The Namespace URI, or the empty string if none + * is available. + * @param localName The element's local (unprefixed) name (required). + * @param qName The element's qualified (prefixed) name, or the + * empty string is none is available. This method will + * use the qName as a template for generating a prefix + * if necessary, but it is not guaranteed to use the + * same qName. + * @param atts The element's attribute list (must not be null). + * @exception org.xml.sax.SAXException If there is an error + * writing the start tag, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#startElement + */ + @Override + public void startElement (String uri, String localName, + String qName, Attributes atts) + throws SAXException + { + closeElement(); + elementLevel++; + nsSupport.pushContext(); + write('<'); + writeName(uri, localName, qName, true); + writeAttributes(atts); + if (elementLevel == 1) { + forceNSDecls(); + } + writeNSDecls(); + openElement = true; + super.startElement(uri, localName, qName, atts); + } + + + /** + * Write an end tag. + * + * Pass the event on down the filter chain for further processing. + * + * @param uri The Namespace URI, or the empty string if none + * is available. + * @param localName The element's local (unprefixed) name (required). + * @param qName The element's qualified (prefixed) name, or the + * empty string is none is available. This method will + * use the qName as a template for generating a prefix + * if necessary, but it is not guaranteed to use the + * same qName. + * @exception org.xml.sax.SAXException If there is an error + * writing the end tag, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#endElement + */ + @Override + public void endElement (String uri, String localName, String qName) + throws SAXException + { + if (openElement) { + write("/>"); + openElement = false; + } else { + write("'); + } + if (elementLevel == 1) { + write('\n'); + } + super.endElement(uri, localName, qName); + nsSupport.popContext(); + elementLevel--; + } + + + /** + * Write character data. + * + * Pass the event on down the filter chain for further processing. + * + * @param ch The array of characters to write. + * @param start The starting position in the array. + * @param length The number of characters to write. + * @exception org.xml.sax.SAXException If there is an error + * writing the characters, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#characters + */ + @Override + public void characters (char ch[], int start, int len) + throws SAXException + { + closeElement(); + writeEsc(ch, start, len, false); + super.characters(ch, start, len); + } + + + /** + * Write ignorable whitespace. + * + * Pass the event on down the filter chain for further processing. + * + * @param ch The array of characters to write. + * @param start The starting position in the array. + * @param length The number of characters to write. + * @exception org.xml.sax.SAXException If there is an error + * writing the whitespace, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#ignorableWhitespace + */ + @Override + public void ignorableWhitespace (char ch[], int start, int length) + throws SAXException + { + closeElement(); + writeEsc(ch, start, length, false); + super.ignorableWhitespace(ch, start, length); + } + + + + /** + * Write a processing instruction. + * + * Pass the event on down the filter chain for further processing. + * + * @param target The PI target. + * @param data The PI data. + * @exception org.xml.sax.SAXException If there is an error + * writing the PI, or if a handler further down + * the filter chain raises an exception. + * @see org.xml.sax.ContentHandler#processingInstruction + */ + @Override + public void processingInstruction (String target, String data) + throws SAXException + { + closeElement(); + write(""); + if (elementLevel < 1) { + write('\n'); + } + super.processingInstruction(target, data); + } + + + + //////////////////////////////////////////////////////////////////// + // Methods from org.xml.sax.ext.LexicalHandler. + //////////////////////////////////////////////////////////////////// + + + /** + * Write start of DOCTYPE declaration. + * + * Pass the event on down the filter chain for further processing. + * + * @param name The document type name. + * @param publicId The declared public identifier for the + * external DTD subset, or null if none was declared. + * @param systemId The declared system identifier for the + * external DTD subset, or null if none was declared. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startDTD + */ + @Override + public void startDTD(String name, String publicId, String systemId) + throws SAXException { + //closeElement(); + inDTD = true; + write("\n\n"); + super.startDTD(name, publicId, systemId); + } + + + /** + * Write end of DOCTYPE declaration. + * + * Pass the event on down the filter chain for further processing. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endDTD + */ + @Override + public void endDTD() + throws SAXException { + inDTD = false; + super.endDTD(); + } + + + /* + * Write entity. + * + * Pass the event on down the filter chain for further processing. + * + * @param name The name of the entity. If it is a parameter + * entity, the name will begin with '%', and if it is the + * external DTD subset, it will be "[dtd]". + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startEntity + */ + @Override + public void startEntity(String name) + throws SAXException { + closeElement(); + write('&'); + write(name); + write(';'); + super.startEntity(name); + } + + + /* + * Filter a end entity event. + * + * Pass the event on down the filter chain for further processing. + * + * @param name The name of the entity that is ending. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#endEntity + */ + @Override + public void endEntity(String name) + throws SAXException { + super.endEntity(name); + } + + + /* + * Write start of CDATA. + * + * Pass the event on down the filter chain for further processing. + * + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#startCDATA + */ + @Override + public void startCDATA() + throws SAXException { + closeElement(); + write(""); + super.endCDATA(); + } + + + /* + * Write a comment. + * + * Pass the event on down the filter chain for further processing. + * + * @param ch An array holding the characters in the comment. + * @param start The starting position in the array. + * @param length The number of characters to use from the array. + * @exception org.xml.sax.SAXException If a filter + * further down the chain raises an exception. + * @see org.xml.sax.ext.LexicalHandler#comment + */ + @Override + public void comment(char[] ch, int start, int length) + throws SAXException { + if (!inDTD) { + closeElement(); + write(""); + if (elementLevel < 1) { + write('\n'); + } + } + super.comment(ch, start, length); + } + + + + //////////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////////// + + + /** + * Force all Namespaces to be declared. + * + * This method is used on the root element to ensure that + * the predeclared Namespaces all appear. + */ + private void forceNSDecls () + { + Iterator prefixes = forcedDeclTable.keySet().iterator(); + while (prefixes.hasNext()) { + String prefix = prefixes.next(); + doPrefix(prefix, null, true); + } + } + + + /** + * Determine the prefix for an element or attribute name. + * + * TODO: this method probably needs some cleanup. + * + * @param uri The Namespace URI. + * @param qName The qualified name (optional); this will be used + * to indicate the preferred prefix if none is currently + * bound. + * @param isElement true if this is an element name, false + * if it is an attribute name (which cannot use the + * default Namespace). + */ + private String doPrefix (String uri, String qName, boolean isElement) + { + String defaultNS = nsSupport.getURI(""); + if ("".equals(uri)) { + if (isElement && defaultNS != null) + nsSupport.declarePrefix("", ""); + return null; + } + String prefix; + if (isElement && defaultNS != null && uri.equals(defaultNS)) { + prefix = ""; + } else { + prefix = nsSupport.getPrefix(uri); + } + if (prefix != null) { + return prefix; + } + prefix = doneDeclTable.get(uri); + if (prefix != null && + ((!isElement || defaultNS != null) && + "".equals(prefix) || nsSupport.getURI(prefix) != null)) { + prefix = null; + } + if (prefix == null) { + prefix = prefixTable.get(uri); + if (prefix != null && + ((!isElement || defaultNS != null) && + "".equals(prefix) || nsSupport.getURI(prefix) != null)) { + prefix = null; + } + } + if (prefix == null && qName != null && !"".equals(qName)) { + int i = qName.indexOf(':'); + if (i == -1) { + if (isElement && defaultNS == null) { + prefix = ""; + } + } else { + prefix = qName.substring(0, i); + } + } + for (; + prefix == null || nsSupport.getURI(prefix) != null; + prefix = "__NS" + ++prefixCounter) + + nsSupport.declarePrefix(prefix, uri); + doneDeclTable.put(uri, prefix); + return prefix; + } + + + /** + * Write a raw character. + * + * @param c The character to write. + * @exception org.xml.sax.SAXException If there is an error writing + * the character, this method will throw an IOException + * wrapped in a SAXException. + */ + private void write (char c) + throws SAXException + { + try { + output.write(c); + } catch (IOException e) { + throw new SAXException(e); + } + } + + + /** + * Write a portion of an array of characters. + * + * @param cbuf Array of characters. + * @param off Offset from which to start writing characters. + * @param len Number of characters to write. + * @exception org.xml.sax.SAXException If there is an error writing + * the character, this method will throw an IOException + * wrapped in a SAXException. + */ + private void write (char[] cbuf, int off, int len) + throws SAXException + { + try { + output.write(cbuf, off, len); + } catch (IOException e) { + throw new SAXException(e); + } + } + + + /** + * Write a raw string. + * + * @param s + * @exception org.xml.sax.SAXException If there is an error writing + * the string, this method will throw an IOException + * wrapped in a SAXException + */ + private void write (String s) + throws SAXException + { + try { + output.write(s); + } catch (IOException e) { + throw new SAXException(e); + } + } + + + /** + * Write out an attribute list, escaping values. + * + * The names will have prefixes added to them. + * + * @param atts The attribute list to write. + * @exception org.xml.SAXException If there is an error writing + * the attribute list, this method will throw an + * IOException wrapped in a SAXException. + */ + private void writeAttributes (Attributes atts) + throws SAXException + { + int len = atts.getLength(); + for (int i = 0; i < len; i++) { + char ch[] = atts.getValue(i).toCharArray(); + write(' '); + writeName(atts.getURI(i), atts.getLocalName(i), + atts.getQName(i), false); + write("=\""); + writeEsc(ch, 0, ch.length, true); + write('"'); + } + } + + + /** + * Write an array of data characters with escaping. + * + * @param ch The array of characters. + * @param start The starting position. + * @param length The number of characters to use. + * @param isAttVal true if this is an attribute value literal. + * @exception org.xml.SAXException If there is an error writing + * the characters, this method will throw an + * IOException wrapped in a SAXException. + */ + private void writeEsc (char ch[], int start, + int length, boolean isAttVal) + throws SAXException + { + for (int i = start; i < start + length; i++) { + switch (ch[i]) { + case '&': + write("&"); + break; + case '<': + write("<"); + break; + case '>': + write(">"); + break; + case '\"': + if (isAttVal) { + write("""); + } else { + write('\"'); + } + break; + default: + if (ch[i] > '\u007f') { + write("&#"); + write(Integer.toString(ch[i])); + write(';'); + } else { + write(ch[i]); + } + } + } + } + + + /** + * Write out the list of Namespace declarations. + * + * @exception org.xml.sax.SAXException This method will throw + * an IOException wrapped in a SAXException if + * there is an error writing the Namespace + * declarations. + */ + private void writeNSDecls () + throws SAXException + { + Enumeration prefixes = nsSupport.getDeclaredPrefixes(); + while (prefixes.hasMoreElements()) { + String prefix = (String) prefixes.nextElement(); + String uri = nsSupport.getURI(prefix); + if (uri == null) { + uri = ""; + } + char ch[] = uri.toCharArray(); + write(' '); + if ("".equals(prefix)) { + write("xmlns=\""); + } else { + write("xmlns:"); + write(prefix); + write("=\""); + } + writeEsc(ch, 0, ch.length, true); + write('\"'); + } + } + + + /** + * Write an element or attribute name. + * + * @param uri The Namespace URI. + * @param localName The local name. + * @param qName The prefixed name, if available, or the empty string. + * @param isElement true if this is an element name, false if it + * is an attribute name. + * @exception org.xml.sax.SAXException This method will throw an + * IOException wrapped in a SAXException if there is + * an error writing the name. + */ + private void writeName (String uri, String localName, + String qName, boolean isElement) + throws SAXException + { + String prefix = doPrefix(uri, qName, isElement); + if (prefix != null && !"".equals(prefix)) { + write(prefix); + write(':'); + } + write(localName); + } + + + /** + * If start element tag is still open, write closing bracket. + */ + private void closeElement() + throws SAXException + { + if (openElement) { + write('>'); + openElement = false; + } + } + + + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + + private Map prefixTable; + private Map forcedDeclTable; + private Map doneDeclTable; + private boolean openElement = false; + private int elementLevel = 0; + private Writer output; + private NamespaceSupport nsSupport; + private int prefixCounter = 0; + private boolean inDTD = false; + +} + +// end of XMLWriter.java diff --git a/core/samples/sax/test1.xml b/core/samples/sax/test1.xml new file mode 100644 index 0000000..1d7c521 --- /dev/null +++ b/core/samples/sax/test1.xml @@ -0,0 +1,4 @@ + + +Jane Smith1965-05-23US + diff --git a/core/samples/sax/test2.xml b/core/samples/sax/test2.xml new file mode 100644 index 0000000..0d85fca --- /dev/null +++ b/core/samples/sax/test2.xml @@ -0,0 +1,9 @@ + + + + Jane Smith + 1965-05-23 + US + + + diff --git a/core/samples/testNamespaces.xml b/core/samples/testNamespaces.xml new file mode 100644 index 0000000..52e15eb --- /dev/null +++ b/core/samples/testNamespaces.xml @@ -0,0 +1,21 @@ + diff --git a/core/samples/web.xml b/core/samples/web.xml new file mode 100644 index 0000000..cfaa5ae --- /dev/null +++ b/core/samples/web.xml @@ -0,0 +1,61 @@ + + + + + + + + snoop + + + SnoopServlet + + + + + file + + + ViewFile + + + + initial + + + 1000 + + + The initial value for the counter + + + + + + mv + + + *.wm + + + + + + + + manager + + + director + + + president + + + + + diff --git a/core/src/java/org/jdom/Attribute.java b/core/src/java/org/jdom/Attribute.java new file mode 100644 index 0000000..f6d09cd --- /dev/null +++ b/core/src/java/org/jdom/Attribute.java @@ -0,0 +1,785 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeMap; + +/** + * An XML attribute. Methods allow the user to obtain the value of the attribute + * as well as namespace and type information. + *

+ * JDOM 1.x Compatibility Note:
+ * The Attribute class in JDOM 1.x had a number of int Constants declared to + * represent different Attribute Types. JDOM2 has introduced an AttributeType + * enumeration instead. To facilitate compatibility and to simplify JDOM 1.x + * migrations, the replacement AttributeType enums are referenced still using + * the JDOM 1.x constant names. In JDOM 1.x these names referenced constant + * int values. In JDOM2 these names reference Enum constants. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Elliotte Rusty Harold + * @author Wesley Biggs + * @author Victor Toni + * @author Rolf Lear + */ +public class Attribute extends CloneBase + implements NamespaceAware, Serializable, Cloneable { + + /** + * JDOM 2.0.0 Serialization version. Attribute is simple + */ + private static final long serialVersionUID = 200L; + + // Keep the old constant names for one beta cycle to help migration + + /** JDOM 1.x compatible reference to {@link AttributeType#UNDECLARED} */ + public final static AttributeType UNDECLARED_TYPE = AttributeType.UNDECLARED; + + /** JDOM 1.x compatible reference to {@link AttributeType#CDATA} */ + public final static AttributeType CDATA_TYPE = AttributeType.CDATA; + + /** JDOM 1.x compatible reference to {@link AttributeType#ID} */ + public final static AttributeType ID_TYPE = AttributeType.ID; + + /** JDOM 1.x compatible reference to {@link AttributeType#IDREF} */ + public final static AttributeType IDREF_TYPE = AttributeType.IDREF; + + /** JDOM 1.x compatible reference to {@link AttributeType#IDREFS} */ + public final static AttributeType IDREFS_TYPE = AttributeType.IDREFS; + + /** JDOM 1.x compatible reference to {@link AttributeType#ENTITY} */ + public final static AttributeType ENTITY_TYPE = AttributeType.ENTITY; + + /** JDOM 1.x compatible reference to {@link AttributeType#ENTITIES} */ + public final static AttributeType ENTITIES_TYPE = AttributeType.ENTITIES; + + /** JDOM 1.x compatible reference to {@link AttributeType#NMTOKEN} */ + public final static AttributeType NMTOKEN_TYPE = AttributeType.NMTOKEN; + + /** JDOM 1.x compatible reference to {@link AttributeType#NMTOKENS} */ + public final static AttributeType NMTOKENS_TYPE = AttributeType.NMTOKENS; + + /** JDOM 1.x compatible reference to {@link AttributeType#NOTATION} */ + public final static AttributeType NOTATION_TYPE = AttributeType.NOTATION; + + /** JDOM 1.x compatible reference to {@link AttributeType#ENUMERATION} */ + public final static AttributeType ENUMERATED_TYPE = AttributeType.ENUMERATION; + + + + + /** The local name of the Attribute */ + protected String name; + + /** The {@link Namespace} of the Attribute */ + protected Namespace namespace; + + /** The value of the Attribute */ + protected String value; + + /** The type of the Attribute */ + protected AttributeType type = AttributeType.UNDECLARED; + + /** + * Specified attributes are part of the XML, + * unspecified attributes are 'defaulted' from a DTD. + */ + protected boolean specified = true; + + /** + * The parent to which this Attribute belongs. Change it with + * {@link #setParent(Element)} + */ + protected transient Element parent; + + /** + * Default, no-args constructor for implementations to use if needed. + */ + protected Attribute() { + } + + /** + * This will create a new Attribute with the + * specified (local) name and value, and in the provided + * {@link Namespace}. + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param namespace Namespace namespace for new attribute. + * @throws IllegalNameException if the given name is illegal as an + * attribute name or if if the new namespace is the default + * namespace. Attributes cannot be in a default namespace. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}). + */ + public Attribute(final String name, final String value, final Namespace namespace) { + this(name, value, AttributeType.UNDECLARED, namespace); + } + + /** + * This will create a new Attribute with the + * specified (local) name, value, and type, and in the provided + * {@link Namespace}. + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type int type for new attribute. + * @param namespace Namespace namespace for new attribute. + * @throws IllegalNameException if the given name is illegal as an + * attribute name or if if the new namespace is the default + * namespace. Attributes cannot be in a default namespace. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}) or + * if the given attribute type is not one of the + * supported types. + * @deprecated Use {@link #Attribute(String, String, AttributeType, Namespace)}. + */ + @Deprecated + public Attribute(final String name, final String value, final int type, final Namespace namespace) { + this(name, value, AttributeType.byIndex(type), namespace); + } + + /** + * This will create a new Attribute with the + * specified (local) name, value, and type, and in the provided + * {@link Namespace}. + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type AttributeType for new attribute. + * @param namespace Namespace namespace for new attribute. + * @throws IllegalNameException if the given name is illegal as an + * attribute name or if if the new namespace is the default + * namespace. Attributes cannot be in a default namespace. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}) or + * if the given attribute type is not one of the + * supported types. + */ + public Attribute(final String name, final String value, final AttributeType type, final Namespace namespace) { + setName(name); + setValue(value); + setAttributeType(type); + setNamespace(namespace); + } + + /** + * This will create a new Attribute with the + * specified (local) name and value, and does not place + * the attribute in a {@link Namespace}. + *

+ * Note: This actually explicitly puts the + * Attribute in the "empty" Namespace + * ({@link Namespace#NO_NAMESPACE}). + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @throws IllegalNameException if the given name is illegal as an + * attribute name. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}). + */ + public Attribute(final String name, final String value) { + this(name, value, AttributeType.UNDECLARED, Namespace.NO_NAMESPACE); + } + + /** + * This will create a new Attribute with the + * specified (local) name, value and type, and does not place + * the attribute in a {@link Namespace}. + *

+ * Note: This actually explicitly puts the + * Attribute in the "empty" Namespace + * ({@link Namespace#NO_NAMESPACE}). + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type AttributeType for new attribute. + * @throws IllegalNameException if the given name is illegal as an + * attribute name. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}) or + * if the given attribute type is not one of the + * supported types. + */ + public Attribute(final String name, final String value, final AttributeType type) { + this(name, value, type, Namespace.NO_NAMESPACE); + } + + /** + * This will create a new Attribute with the + * specified (local) name, value and type, and does not place + * the attribute in a {@link Namespace}. + *

+ * Note: This actually explicitly puts the + * Attribute in the "empty" Namespace + * ({@link Namespace#NO_NAMESPACE}). + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type int type for new attribute. + * @throws IllegalNameException if the given name is illegal as an + * attribute name. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}) or + * if the given attribute type is not one of the + * supported types. + * @deprecated Use {@link #Attribute(String, String, AttributeType)} + */ + @Deprecated + public Attribute(final String name, final String value, final int type) { + this(name, value, type, Namespace.NO_NAMESPACE); + } + + /** + * This will return the parent of this Attribute. + * If there is no parent, then this returns null. + * Use return-type covariance to override Content's getParent() method + * to return an Element, not just a Parent + * + * @return parent of this Attribute + */ + public Element getParent() { + return parent; + } + + /** + * Get this Attribute's Document. + * @return The document to which this Attribute is associated, may be null. + */ + public Document getDocument() { + return parent == null ? null : parent.getDocument(); + } + + /** + * This will retrieve the local name of the + * Attribute. For any XML attribute + * which appears as + * [namespacePrefix]:[attributeName], + * the local name of the attribute would be + * [attributeName]. When the attribute + * has no namespace, the local name is simply the attribute + * name. + *

+ * To obtain the namespace prefix for this + * attribute, the + * {@link #getNamespacePrefix()} + * method should be used. + * + * @return String - name of this attribute, + * without any namespace prefix. + */ + public String getName() { + return name; + } + + /** + * This sets the local name of the Attribute. + * + * @param name the new local name to set + * @return Attribute - the attribute modified. + * @throws IllegalNameException if the given name is illegal as an + * attribute name. + */ + public Attribute setName(final String name) { + if (name == null) { + throw new NullPointerException( + "Can not set a null name for an Attribute."); + } + final String reason = Verifier.checkAttributeName(name); + if (reason != null) { + throw new IllegalNameException(name, "attribute", reason); + } + this.name = name; + specified = true; + return this; + } + + /** + * This will retrieve the qualified name of the Attribute. + * For any XML attribute whose name is + * [namespacePrefix]:[elementName], + * the qualified name of the attribute would be + * everything (both namespace prefix and + * element name). When the attribute has no + * namespace, the qualified name is simply the attribute's + * local name. + *

+ * To obtain the local name of the attribute, the + * {@link #getName()} method should be used. + *

+ * To obtain the namespace prefix for this attribute, + * the {@link #getNamespacePrefix()} + * method should be used. + * + * @return String - full name for this element. + */ + public String getQualifiedName() { + // Note: Any changes here should be reflected in + // XMLOutputter.printQualifiedName() + final String prefix = namespace.getPrefix(); + + // no prefix found + if ("".equals(prefix)) { + return getName(); + } + return new StringBuilder(prefix) + .append(':') + .append(getName()) + .toString(); + } + + /** + * This will retrieve the namespace prefix of the + * Attribute. For any XML attribute + * which appears as + * [namespacePrefix]:[attributeName], + * the namespace prefix of the attribute would be + * [namespacePrefix]. When the attribute + * has no namespace, an empty String is returned. + * + * @return String - namespace prefix of this + * attribute. + */ + public String getNamespacePrefix() { + return namespace.getPrefix(); + } + + /** + * This returns the URI mapped to this Attribute's + * prefix. If no mapping is found, an empty String is + * returned. + * + * @return String - namespace URI for this Attribute. + */ + public String getNamespaceURI() { + return namespace.getURI(); + } + + /** + * This will return this Attribute's + * {@link Namespace}. + * + * @return Namespace - Namespace object for this Attribute + */ + public Namespace getNamespace() { + return namespace; + } + + /** + * This sets this Attribute's {@link Namespace}. + * If the provided namespace is null, the attribute will have no namespace. + * The namespace must have a prefix. + * + * @param namespace the new namespace + * @return Element - the element modified. + * @throws IllegalNameException if the new namespace is the default + * namespace. Attributes cannot be in a default namespace. + */ + public Attribute setNamespace(Namespace namespace) { + if (namespace == null) { + namespace = Namespace.NO_NAMESPACE; + } + + // Verify the attribute isn't trying to be in a default namespace + // Attributes can't be in a default namespace + if (namespace != Namespace.NO_NAMESPACE && + "".equals(namespace.getPrefix())) { + throw new IllegalNameException("", "attribute namespace", + "An attribute namespace without a prefix can only be the " + + "NO_NAMESPACE namespace"); + } + this.namespace = namespace; + specified = true; + return this; + } + + /** + * This will return the actual textual value of this + * Attribute. This will include all text + * within the quotation marks. + * + * @return String - value for this attribute. + */ + public String getValue() { + return value; + } + + /** + * This will set the value of the Attribute. + * + * @param value String value for the attribute. + * @return Attribute - this Attribute modified. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}). + */ + public Attribute setValue(final String value) { + if (value == null) { + throw new NullPointerException( + "Can not set a null value for an Attribute"); + } + final String reason = Verifier.checkCharacterData(value); + if (reason != null) { + throw new IllegalDataException(value, "attribute", reason); + } + this.value = value; + specified = true; + return this; + } + + /** + * This will return the declared type of this Attribute. + * + * @return AttributeType - type for this attribute. + */ + public AttributeType getAttributeType() { + return type; + } + + /** + * This will set the type of the Attribute. + * + * @param type int type for the attribute. + * @return Attribute - this Attribute modified. + * @throws IllegalDataException if the given attribute type is + * not one of the supported types. + */ + public Attribute setAttributeType(final AttributeType type) { + this.type = type == null ? AttributeType.UNDECLARED : type; + specified = true; + return this; + } + + /** + * This will set the type of the Attribute. + * + * @param type int type for the attribute. + * @return Attribute - this Attribute modified. + * @throws IllegalDataException if the given attribute type is + * not one of the supported types. + * @deprecated use {@link #setAttributeType(AttributeType)} + */ + @Deprecated + public Attribute setAttributeType(final int type) { + setAttributeType(AttributeType.byIndex(type)); + return this; + } + + /** + * Get the 'specified' flag. True values indicate this attribute + * was part of an XML document, false indicates it was defaulted + * from a DTD. + * @return the specified flag. + * @since JDOM2 + */ + public boolean isSpecified() { + return specified; + } + + /** + * Change the specified flag to the given value. + * @param specified The value to set the specified flag to. + * @since JDOM2 + */ + public void setSpecified(boolean specified) { + this.specified = specified; + } + + /** + * This returns a String representation of the + * Attribute, suitable for debugging. + * + * @return String - information about the + * Attribute + */ + @Override + public String toString() { + return new StringBuilder() + .append("[Attribute: ") + .append(getQualifiedName()) + .append("=\"") + .append(value) + .append("\"") + .append("]") + .toString(); + } + + @Override + public Attribute clone() { + final Attribute clone = (Attribute) super.clone(); + clone.parent = null; + return clone; + } + + /** + * Detach this Attribute from its parent. + * @return this Attribute (detached). + */ + public Attribute detach() { + if (parent != null) { + parent.removeAttribute(this); + } + return this; + } + + /** + * Set this Attribute's parent. This is not public! + * @param parent The parent to set + * @return this Attribute (state may be indeterminate depending on whether + * this has been included in the Element's list yet). + */ + protected Attribute setParent(Element parent) { + this.parent = parent; + return this; + } + + + ///////////////////////////////////////////////////////////////// + // Convenience Methods below here + ///////////////////////////////////////////////////////////////// + + /** + * This gets the value of the attribute, in + * int form, and if no conversion + * can occur, throws a + * {@link DataConversionException} + * + * @return int value of attribute. + * @throws DataConversionException when conversion fails. + */ + public int getIntValue() throws DataConversionException { + try { + return Integer.parseInt(value.trim()); + } catch (final NumberFormatException e) { + throw new DataConversionException(name, "int"); + } + } + + /** + * This gets the value of the attribute, in + * long form, and if no conversion + * can occur, throws a + * {@link DataConversionException} + * + * @return long value of attribute. + * @throws DataConversionException when conversion fails. + */ + public long getLongValue() throws DataConversionException { + try { + return Long.parseLong(value.trim()); + } catch (final NumberFormatException e) { + throw new DataConversionException(name, "long"); + } + } + + /** + * This gets the value of the attribute, in + * float form, and if no conversion + * can occur, throws a + * {@link DataConversionException} + * + * @return float value of attribute. + * @throws DataConversionException when conversion fails. + */ + public float getFloatValue() throws DataConversionException { + try { + // Avoid Float.parseFloat() to support JDK 1.1 + return Float.valueOf(value.trim()).floatValue(); + } catch (final NumberFormatException e) { + throw new DataConversionException(name, "float"); + } + } + + /** + * This gets the value of the attribute, in + * double form, and if no conversion + * can occur, throws a + * {@link DataConversionException} + * + * @return double value of attribute. + * @throws DataConversionException when conversion fails. + */ + public double getDoubleValue() throws DataConversionException { + try { + // Avoid Double.parseDouble() to support JDK 1.1 + return Double.valueOf(value.trim()).doubleValue(); + } catch (final NumberFormatException e) { + // Specially handle INF and -INF that Double.valueOf doesn't do + String v = value.trim(); + if ("INF".equals(v)) { + return Double.POSITIVE_INFINITY; + } + if ("-INF".equals(v)) { + return Double.NEGATIVE_INFINITY; + } + throw new DataConversionException(name, "double"); + } + } + + /** + * This gets the effective boolean value of the attribute, or throws a + * {@link DataConversionException} if a conversion can't be + * performed. True values are: "true", "on", "1", and "yes". False + * values are: "false", "off", "0", and "no". Values are trimmed before + * comparison. Values other than those listed here throw the exception. + * + * @return boolean value of attribute. + * @throws DataConversionException when conversion fails. + */ + public boolean getBooleanValue() throws DataConversionException { + final String valueTrim = value.trim(); + if ( + (valueTrim.equalsIgnoreCase("true")) || + (valueTrim.equalsIgnoreCase("on")) || + (valueTrim.equalsIgnoreCase("1")) || + (valueTrim.equalsIgnoreCase("yes"))) { + return true; + } else if ( + (valueTrim.equalsIgnoreCase("false")) || + (valueTrim.equalsIgnoreCase("off")) || + (valueTrim.equalsIgnoreCase("0")) || + (valueTrim.equalsIgnoreCase("no")) + ) { + return false; + } else { + throw new DataConversionException(name, "boolean"); + } + } + + /** + * Get the namespaces that are in-scope on this Attribute. + *

+ * Attribute has peculiarities that affect the in-scope Namespaces because + * there are conditions in which the Attribute's scope is different to its + * parent Element's scope. Specifically, if the parent Element is in a + * 'default' Namespace that is not the empty Namespace (e.g. + * xmlns="someurl") and this Attribute is also in the default Namespace (has + * no prefix - but for Attributes that means the Namespace URL is ""), then + * this Attribute has a different namespace scope from it's parent Element + * because it does not include the 'someurl' Namespace. + *

+ * In the above conditions (no-prefix Attribute in an Element with a + * non-empty no-prefix Namespace) this Attribute effectively re-binds the "" + * prefix to the "" URL, thus the Attribute 'introduces' the Namespace. + * It follows then that the getNamespacesIntroduced() will return a list + * with the single member {@link Namespace#NO_NAMESPACE}. + *

+ * Note that the Attribute's Namespace will always be reported first. + *

+ * Description copied from + * {@link NamespaceAware#getNamespacesInScope()}: + *

+ * {@inheritDoc} + */ + @Override + public List getNamespacesInScope() { + if (getParent() == null) { + ArrayList ret = new ArrayList(3); + ret.add(getNamespace()); + ret.add(Namespace.XML_NAMESPACE); + return Collections.unmodifiableList(ret); + } + return orderFirst(getNamespace(), getParent().getNamespacesInScope()); + } + + @Override + public List getNamespacesIntroduced() { + if (getParent() == null) { + return Collections.singletonList(getNamespace()); + } + return Collections.emptyList(); + } + + @Override + public List getNamespacesInherited() { + if (getParent() == null) { + return Collections.singletonList(Namespace.XML_NAMESPACE); + } + return orderFirst(getNamespace(), getParent().getNamespacesInScope()); + } + + + private static final List orderFirst(final Namespace nsa, + final List nsl) { + if (nsl.get(0) == nsa) { + return nsl; + } + // OK, we have our namespace list, but our's is not the first. + // we need the Attribute's Namespace to be up front. + TreeMap tm = new TreeMap(); + for (Namespace ns : nsl) { + if (ns != nsa) { + tm.put(ns.getPrefix(), ns); + } + } + ArrayList ret = new ArrayList(tm.size() + 1); + ret.add(nsa); + ret.addAll(tm.values()); + return Collections.unmodifiableList(ret); + } + +} diff --git a/core/src/java/org/jdom/AttributeList.java b/core/src/java/org/jdom/AttributeList.java new file mode 100644 index 0000000..60ef067 --- /dev/null +++ b/core/src/java/org/jdom/AttributeList.java @@ -0,0 +1,696 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +import org.jdom.internal.ArrayCopy; + +/** + * AttributeList represents legal JDOM + * {@link Attribute} content. + *

+ * This class is NOT PUBLIC; users should see it as a simple List + * implementation, although it behaves something like a Set because you cannot + * add duplicate Attributes. An attribute is considered duplicate if it has the + * same Namespace URI and Attribute name as another existing Attribute. + * + * @author Alex Rosen + * @author Philippe Riand + * @author Bradley S. Huffman + * @author Rolf Lear + */ +final class AttributeList extends AbstractList + implements RandomAccess { + + /** The initial size to start the backing array. */ + private static final int INITIAL_ARRAY_SIZE = 4; + + /** The backing array */ + private Attribute attributeData[]; + + /** The current size */ + private int size; + + /** The parent Element */ + private final Element parent; + + private static final Comparator ATTRIBUTE_NATURAL = new Comparator() { + + @Override + public int compare(Attribute a1, Attribute a2) { + int pcomp = a1.getNamespacePrefix().compareTo(a2.getNamespacePrefix()); + if (pcomp != 0) { + return pcomp; + } + return a1.getName().compareTo(a2.getName()); + } + + }; + + /** + * Create a new instance of the AttributeList representing parent + * Element's Attributes + * + * @param parent + * Element whose Attributes are to be held + */ + AttributeList(final Element parent) { + this.parent = parent; + } + + /** + * Package internal method to support building from sources that are 100% + * trusted. + * + * @param a + * an Attribute to add without any checks + */ + final void uncheckedAddAttribute(final Attribute a) { + a.parent = parent; + ensureCapacity(size + 1); + attributeData[size++] = a; + modCount++; + } + + /** + * Check and add attribute to the end of the list or replace an + * existing Attribute with the same name and + * Namespace. + * + * @param attribute + * The Attribute to insert into the list. + * @return true as specified by Collection.add(). + * @throws IllegalAddException + * if validation rules prevent the add + */ + @Override + public boolean add(final Attribute attribute) { + if (attribute.getParent() != null) { + throw new IllegalAddException( + "The attribute already has an existing parent \"" + + attribute.getParent().getQualifiedName() + "\""); + } + + if (Verifier.checkNamespaceCollision(attribute, parent) != null) { + throw new IllegalAddException(parent, attribute, + Verifier.checkNamespaceCollision(attribute, parent)); + } + + // returns -1 if not exist + final int duplicate = indexOfDuplicate(attribute); + if (duplicate < 0) { + attribute.setParent(parent); + ensureCapacity(size + 1); + attributeData[size++] = attribute; + modCount++; + } else { + final Attribute old = attributeData[duplicate]; + old.setParent(null); + attributeData[duplicate] = attribute; + attribute.setParent(parent); + } + return true; + } + + /** + * Check and add attribute to this list at index. + * + * @param index + * where to add/insert the Attribute + * @param attribute + * Attribute to add + * @throws IllegalAddException + * if validation rules prevent the add + */ + @Override + public void add(final int index, final Attribute attribute) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Index: " + index + + " Size: " + size()); + } + + if (attribute.getParent() != null) { + throw new IllegalAddException( + "The attribute already has an existing parent \"" + + attribute.getParent().getQualifiedName() + "\""); + } + final int duplicate = indexOfDuplicate(attribute); + if (duplicate >= 0) { + throw new IllegalAddException("Cannot add duplicate attribute"); + } + + final String reason = Verifier.checkNamespaceCollision(attribute, parent); + if (reason != null) { + throw new IllegalAddException(parent, attribute, reason); + } + + attribute.setParent(parent); + + ensureCapacity(size + 1); + if (index == size) { + attributeData[size++] = attribute; + } else { + System.arraycopy(attributeData, index, attributeData, index + 1, + size - index); + attributeData[index] = attribute; + size++; + } + modCount++; + } + + /** + * Add all the Attributes in collection. + * + * @param collection + * The Collection of Attributes to add. + * @return true if the list was modified as a result of the + * add. + * @throws IllegalAddException + * if validation rules prevent the addAll + */ + @Override + public boolean addAll(final Collection collection) { + return addAll(size(), collection); + } + + /** + * Inserts the Attributes in collection at the specified + * index in this list. + * + * @param index + * The offset at which to start adding the Attributes + * @param collection + * The Collection containing the Attributes + * to add. + * @return true if the list was modified as a result of the + * add. + * @throws IllegalAddException + * if validation rules prevent the addAll + */ + @Override + public boolean addAll(final int index, + final Collection collection) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException("Index: " + index + + " Size: " + size()); + } + + if (collection == null) { + throw new NullPointerException( + "Can not add a null Collection to AttributeList"); + } + final int addcnt = collection.size(); + if (addcnt == 0) { + return false; + } + if (addcnt == 1) { + // quick check for single-add. + add(index, collection.iterator().next()); + return true; + } + + ensureCapacity(size() + addcnt); + + final int tmpmodcount = modCount; + boolean ok = false; + + int count = 0; + + try { + for (Attribute att : collection) { + add(index + count, att); + count++; + } + ok = true; + } finally { + if (!ok) { + while (--count >= 0) { + remove(index + count); + } + modCount = tmpmodcount; + } + } + + return true; + } + + /** + * Clear the current list. + */ + @Override + public void clear() { + if (attributeData != null) { + while (size > 0) { + size--; + attributeData[size].setParent(null); + attributeData[size] = null; + } + } + modCount++; + } + + /** + * Clear the current list and set it to the contents of collection. + * + * @param collection + * The Collection to use. + * @throws IllegalAddException + * if validation rules prevent the addAll + */ + void clearAndSet(final Collection collection) { + if (collection == null || collection.isEmpty()) { + clear(); + return; + } + + // keep a backup in case we need to roll-back... + final Attribute[] old = attributeData; + final int oldSize = size; + final int oldModCount = modCount; + + // clear the current system + // we need to detatch before we add so that we don't run in to a problem + // where an attribute in the to-add list is one that we are 'clearing' + // first. + while (size > 0) { + old[--size].setParent(null); + } + size = 0; + attributeData = null; + + boolean ok = false; + try { + addAll(0, collection); + ok = true; + } finally { + if (!ok) { + // we have an exception pending.... + // restore the old system. + // re-attach the old stuff + attributeData = old; + while (size < oldSize) { + attributeData[size++].setParent(parent); + } + modCount = oldModCount; + } + } + + } + + /** + * Increases the capacity of this AttributeList instance, if + * necessary, to ensure that it can hold at least the number of items + * specified by the minimum capacity argument. + * + * @param minCapacity + * the desired minimum capacity. + */ + private void ensureCapacity(final int minCapacity) { + if (attributeData == null) { + attributeData = + new Attribute[Math.max(minCapacity, INITIAL_ARRAY_SIZE)]; + return; + } else if (minCapacity < attributeData.length) { + return; + } + // most JVM's allocate memory in multiples of 'double-words', on + // 64-bit it's 16-bytes, on 32-bit it's 8 bytes which all means it makes + // sense to increment the capacity in even values. + attributeData = ArrayCopy.copyOf(attributeData, + ((minCapacity + INITIAL_ARRAY_SIZE) >>> 1) << 1); + } + + /** + * Retrieve the Attribute at offset. + * + * @param index + * The position of the Attribute to retrieve. + * @return The Attribute at position index. + */ + @Override + public Attribute get(final int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index: " + index + + " Size: " + size()); + } + + return attributeData[index]; + } + + /** + * Retrieve the Attribute with the given name and the same + * Namespace URI as namespace. + * + * @param name + * name of attribute to return + * @param namespace + * indicate what Namespace URI to consider + * @return the Attribute, or null if one doesn't exist. + */ + Attribute get(final String name, final Namespace namespace) { + final int index = indexOf(name, namespace); + if (index < 0) { + return null; + } + return attributeData[index]; + } + + /** + * Return index of the Attribute with the given name and + * the same Namespace URI as namespace. + * + * @param name + * name of Attribute to retrieve + * @param namespace + * indicate what Namespace URI to consider + * @return the index of the attribute that matches the conditions, or + * -1 if there is none. + */ + int indexOf(final String name, final Namespace namespace) { + if (attributeData != null) { + if (namespace == null) { + return indexOf(name, Namespace.NO_NAMESPACE); + } + final String uri = namespace.getURI(); + for (int i = 0; i < size; i++) { + final Attribute att = attributeData[i]; + if (uri.equals(att.getNamespaceURI()) && + name.equals(att.getName())) { + return i; + } + } + } + return -1; + } + + /** + * Remove the Attribute at index. + * + * @param index + * The offset of the Attribute to remove. + * @return The removed Attribute. + */ + @Override + public Attribute remove(final int index) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index: " + index + + " Size: " + size()); + + final Attribute old = attributeData[index]; + old.setParent(null); + System.arraycopy(attributeData, index + 1, attributeData, index, + size - index - 1); + attributeData[--size] = null; // Let gc do its work + modCount++; + return old; + } + + /** + * Remove the Attribute with the specified name and the same + * URI as namespace. + * + * @param name + * name of Attribute to remove + * @param namespace + * indicate what Namespace URI to consider + * @return the true if attribute was removed, + * false otherwise + */ + boolean remove(final String name, final Namespace namespace) { + final int index = indexOf(name, namespace); + if (index < 0) { + return false; + } + remove(index); + return true; + } + + /** + * Set the Attribute at index to be attribute. + * + * @param index + * The location to set the value to. + * @param attribute + * The Attribute to set. + * @return The replaced Attribute. + * @throws IllegalAddException + * if validation rules prevent the set + */ + @Override + public Attribute set(final int index, final Attribute attribute) { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException("Index: " + index + + " Size: " + size()); + + if (attribute.getParent() != null) { + throw new IllegalAddException( + "The attribute already has an existing parent \"" + + attribute.getParent().getQualifiedName() + "\""); + } + + final int duplicate = indexOfDuplicate(attribute); + if ((duplicate >= 0) && (duplicate != index)) { + throw new IllegalAddException("Cannot set duplicate attribute"); + } + + final String reason = Verifier.checkNamespaceCollision(attribute, parent, index); + if (reason != null) { + throw new IllegalAddException(parent, attribute, reason); + } + + final Attribute old = attributeData[index]; + old.setParent(null); + + attributeData[index] = attribute; + attribute.setParent(parent); + return old; + } + + /** + * Return index of attribute with same name and Namespace, or -1 if one + * doesn't exist + */ + private int indexOfDuplicate(final Attribute attribute) { + return indexOf(attribute.getName(), attribute.getNamespace()); + } + + /** + * Returns an Iterator over the Attributes in this + * list in the proper sequence. + * + * @return an iterator. + */ + @Override + public Iterator iterator() { + return new ALIterator(); + } + + /** + * Return the number of Attributes in this list + * + * @return The number of Attributes in this list. + */ + @Override + public int size() { + return size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + /** + * Return this list as a String + */ + @Override + public String toString() { + return super.toString(); + } + + /** + * Unlike the Arrays.binarySearch, this method never expects an + * "already exists" condition, we only ever add, thus there will never + * be a negative insertion-point. + * @param indexes The pointers to search within + * @param len The number of pointers to search within + * @param val The pointer we are checking for. + * @param comp The Comparator to compare with + * @return the insertion point. + */ + private final int binarySearch(final int[] indexes, final int len, + final int val, final Comparator comp) { + int left = 0, mid = 0, right = len - 1, cmp = 0; + final Attribute base = attributeData[val]; + while (left <= right) { + mid = (left + right) >>> 1; + cmp = comp.compare(base, attributeData[indexes[mid]]); + if (cmp == 0) { + while (cmp == 0 && mid < right && comp.compare( + base, attributeData[indexes[mid + 1]]) == 0) { + mid++; + } + return mid + 1; + } else if (cmp < 0) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; + } + + private void sortInPlace(final int[] indexes) { + // the indexes are a discrete set of values that have no duplicates, + // and describe the relative order of each of them. + // as a result, we can do some tricks.... + final int[] unsorted = ArrayCopy.copyOf(indexes, indexes.length); + Arrays.sort(unsorted); + final Attribute[] usc = new Attribute[unsorted.length]; + for (int i = 0; i < usc.length; i++) { + usc[i] = attributeData[indexes[i]]; + } + // usc contains the content in their pre-sorted order.... + for (int i = 0; i < indexes.length; i ++) { + attributeData[unsorted[i]] = usc[i]; + } + } + + /** + * Sort the attributes using the supplied comparator. The attributes are + * never added using regular mechanisms, so there are never problems with + * detached or already-attached Attributes. The sort happens 'in place'. + *

+ * If the comparator identifies two (or more) Attributes to be equal, then + * the relative order of those attributes will not be changed. + * + * @param comp The Comparator to use for sorting. + */ + public void sort(Comparator comp) { + if (comp == null) { + comp = ATTRIBUTE_NATURAL; + } + final int sz = size; + int[] indexes = new int[sz]; + for (int i = 0 ; i < sz; i++) { + final int ip = binarySearch(indexes, i, i, comp); + if (ip < i) { + System.arraycopy(indexes, ip, indexes, ip+1, i - ip); + } + indexes[ip] = i; + } + sortInPlace(indexes); + } + + /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * */ + /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * */ + /** + * A fast iterator that can beat AbstractList because we can access the data + * directly. This is important because so much code now uses the for-each + * type loop for (Attribute a : element.getAttributes()) {...}, + * and that uses iterator(). + * + * @author Rolf Lear + */ + private final class ALIterator implements Iterator { + // The modCount to expect (or throw ConcurrentModeEx) + private int expect = -1; + // the index of the next Attribute to return. + private int cursor = 0; + // whether it is legal to call remove() + private boolean canremove = false; + + private ALIterator() { + expect = modCount; + } + + @Override + public boolean hasNext() { + return cursor < size; + } + + @Override + public Attribute next() { + if (modCount != expect) { + throw new ConcurrentModificationException("ContentList was " + + "modified outside of this Iterator"); + } + if (cursor >= size) { + throw new NoSuchElementException("Iterated beyond the end of " + + "the ContentList."); + } + canremove = true; + return attributeData[cursor++]; + } + + @Override + public void remove() { + if (modCount != expect) { + throw new ConcurrentModificationException("ContentList was " + + "modified outside of this Iterator"); + } + if (!canremove) { + throw new IllegalStateException("Can only remove() content " + + "after a call to next()"); + } + AttributeList.this.remove(--cursor); + expect = modCount; + canremove = false; + } + + } + +} diff --git a/core/src/java/org/jdom/AttributeType.java b/core/src/java/org/jdom/AttributeType.java new file mode 100644 index 0000000..da6e945 --- /dev/null +++ b/core/src/java/org/jdom/AttributeType.java @@ -0,0 +1,203 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.xml.sax.Attributes; + +/** + * Use a simple enumeration for the Attribute Types + * + * @author Rolf Lear + * + */ +public enum AttributeType { + + /** + * Attribute type: the attribute has not been declared or type + * is unknown. + * + * @see #getAttributeType + */ + UNDECLARED, + + /** + * Attribute type: the attribute value is a string. + * + * @see Attribute#getAttributeType + */ + CDATA, + + /** + * Attribute type: the attribute value is a unique identifier. + * + * @see #getAttributeType + */ + ID, + + /** + * Attribute type: the attribute value is a reference to a + * unique identifier. + * + * @see #getAttributeType + */ + IDREF, + + /** + * Attribute type: the attribute value is a list of references to + * unique identifiers. + * + * @see #getAttributeType + */ + IDREFS, + /** + * Attribute type: the attribute value is the name of an entity. + * + * @see #getAttributeType + */ + ENTITY, + /** + *

+ * Attribute type: the attribute value is a list of entity names. + *

+ * + * @see #getAttributeType + */ + ENTITIES, + + /** + * Attribute type: the attribute value is a name token. + *

+ * According to SAX 2.0 specification, attributes of enumerated + * types should be reported as "NMTOKEN" by SAX parsers. But the + * major parsers (Xerces and Crimson) provide specific values + * that permit to recognize them as {@link #ENUMERATION}. + * + * @see Attribute#getAttributeType + */ + NMTOKEN, + /** + * Attribute type: the attribute value is a list of name tokens. + * + * @see #getAttributeType + */ + NMTOKENS, + /** + * Attribute type: the attribute value is the name of a notation. + * + * @see #getAttributeType + */ + NOTATION, + /** + * Attribute type: the attribute value is a name token from an + * enumeration. + * + * @see #getAttributeType + */ + ENUMERATION; + + /** + * Obtain a AttributeType by a int constant. + * This goes against the logic of enums, but this is used for backward + * compatibility. Thus, this method is marked Deprecated. + * @param index The AttributeType int equivalent to retrieve + * @return The AttributeType corresponding to the specified equivalent + * @throws IllegalArgumentException if there is no equivalent + * @deprecated Use normal Enums instead of int's + */ + @Deprecated + public static final AttributeType byIndex(int index) { + if (index < 0) { + throw new IllegalDataException("No such AttributeType " + index); + } + if (index >= values().length) { + throw new IllegalDataException("No such AttributeType " + + index + ", max is " + (values().length - 1)); + } + return values()[index]; + } + + /** + * Returns the the JDOM AttributeType value from the SAX 2.0 + * attribute type string provided by the parser. + * + * @param typeName String the SAX 2.0 attribute + * type string. + * + * @return int the JDOM attribute type. + * + * @see Attribute#setAttributeType + * @see Attributes#getType + */ + public static final AttributeType getAttributeType(String typeName) { + if (typeName == null) { + return UNDECLARED; + } + + try { + return valueOf(typeName); + } catch (IllegalArgumentException iae) { + if (typeName.length() > 0 && + typeName.trim().charAt(0) == '(') { + // Xerces 1.4.X reports attributes of enumerated type with + // a type string equals to the enumeration definition, i.e. + // starting with a parenthesis. + return ENUMERATION; + } + return UNDECLARED; + } + + } + +} diff --git a/core/src/java/org/jdom/CDATA.java b/core/src/java/org/jdom/CDATA.java new file mode 100644 index 0000000..504266c --- /dev/null +++ b/core/src/java/org/jdom/CDATA.java @@ -0,0 +1,230 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.jdom.output.XMLOutputter2; + +/** + * An XML CDATA section. Represents character-based content within an XML + * document that should be output within special CDATA tags. Semantically it's + * identical to a simple {@link Text} object, but output behavior is different. + * CDATA makes no guarantees about the underlying textual representation of + * character data, but does expose that data as a Java String. + * + * @author Dan Schaffer + * @author Brett McLaughlin + * @author Jason Hunter + * @author Bradley S. Huffman + * @author Victor Toni + * @author Rolf Lear + */ +public class CDATA extends Text { + + /** + * JDOM 2.0.0 Serialization version. CDATA is simple + */ + private static final long serialVersionUID = 200L; + + /** + * This is the protected, no-args constructor standard in all JDOM + * classes. It allows subclassers to get a raw instance with no + * initialization. + */ + protected CDATA() { + super(CType.CDATA); + } + + /** + * This constructor creates a new CDATA node, with the + * supplied string value as it's character content. + * + * @param string the node's character content. + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + * or the CDATA end delimiter ]]>. + */ + public CDATA(final String string) { + super(CType.CDATA); + setText(string); + } + + /** + * This will set the value of this CDATA node. + * + * @param str value for node's content. + * @return the object on which the method was invoked + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + * or the CDATA end delimiter ]]>. + */ + @Override + public CDATA setText(final String str) { + // Overrides Text.setText() because this needs to check that CDATA rules + // are enforced. We could have a separate Verifier check for CDATA + // beyond Text and call that alone before super.setText(). + + if (str == null || "".equals(str)) { + value = EMPTY_STRING; + return this; + } + + final String reason = Verifier.checkCDATASection(str); + if (reason != null) { + throw new IllegalDataException(str, "CDATA section", reason); + } + + value = str; + + return this; + } + + /** + * This will append character content to whatever content already + * exists within this CDATA node. + * + * @param str character content to append. + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + * or the CDATA end delimiter ]]>. + */ + @Override + public void append(final String str) { + // Overrides Text.append(String) because this needs to check that CDATA + // rules are enforced. We could have a separate Verifier check for CDATA + // beyond Text and call that alone before super.setText(). + + if (str == null || "".equals(str)) { + return; + } + + // we need a temp value to ensure that the value is changed _after_ + // validation + final String tmpValue; + if (value == EMPTY_STRING) { + tmpValue = str; + } else { + tmpValue = value + str; + } + + // we have to do late checking since the end of a CDATA section could + // have been created by concating both strings: + // "]" + "]>" + // or + // "]]" + ">" + // TODO: maybe this could be optimized for this two cases + final String reason = Verifier.checkCDATASection(tmpValue); + if (reason != null) { + throw new IllegalDataException(str, "CDATA section", reason); + } + + value = tmpValue; + } + + /** + * This will append the content of another Text node + * to this node. + * + * @param text Text node to append. + */ + @Override + public void append(final Text text) { + // Overrides Text.append(Text) because this needs to check that CDATA + // rules are enforced. We could have a separate Verifier check for CDATA + // beyond Text and call that alone before super.setText(). + + if (text == null) { + return; + } + append(text.getText()); + } + + /** + * This returns a String representation of the + * CDATA node, suitable for debugging. If the XML + * representation of the CDATA node is desired, + * either {@link #getText} or + * {@link XMLOutputter2#output(CDATA, java.io.Writer)} + * should be used. + * + * @return String - information about this node. + */ + @Override + public String toString() { + return new StringBuilder(64) + .append("[CDATA: ") + .append(getText()) + .append("]") + .toString(); + } + + @Override + public CDATA clone() { + return (CDATA)super.clone(); + } + + @Override + public CDATA detach() { + return (CDATA)super.detach(); + } + + @Override + protected CDATA setParent(Parent parent) { + return (CDATA)super.setParent(parent); + } + +} diff --git a/core/src/java/org/jdom/CloneBase.java b/core/src/java/org/jdom/CloneBase.java new file mode 100644 index 0000000..70066bb --- /dev/null +++ b/core/src/java/org/jdom/CloneBase.java @@ -0,0 +1,116 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * This simple class just tidies up any cloneable classes. This method deals + * with any CloneNotSupported exceptions. THis class is package private only. + * + * @author Rolf Lear + */ +class CloneBase implements Cloneable { + + /** + * Change the permission of the no-arg constructor from public to protcted. + *

+ * Otherwise package-private class's constructor is not really public. Changing this to + * 'protected' makes this constructor available to all subclasses regardless of the + * subclass's package. This in turn makes it possible to make all th subclasses of this + * CloneBase class serializable. + * + */ + protected CloneBase() { + // This constructor is needed to solve issue #88 + // There needs to be a no-arg constructor accessible by all + // potential subclasses of CloneBase, and 'protected' is actually more + // accessible than 'public' since this is a package-private class. + } + + /** + * Return a deep clone of this instance. Even if this instance has a parent, + * the returned clone will not. + *

+ * All JDOM core classes are Cloneable, and never throw + * CloneNotSupportedException. Additionally all Cloneable JDOM classes + * return the correct type of instance from this method and there is no + * need to cast the result (co-variant return vaue). + *

+ * Subclasses of this should still call super.clone() in their clone method. + */ + @Override + protected CloneBase clone() { + /* + * Additionally, when you use the concept of 'co-variant return values' + * you create 'bridge' methods. By way of example, because we change the + * return type of clone() from Object to CloneBase, Java is forced to + * put in a 'bridge' method that has an Object return type, even though + * we never actually call it.

This has an impact on the code + * coverage tool Cobertura, which reports that there is missed code (and + * there is, the bridge method). It reports it as being '0' calls to the + * 'class' line (the class line is marked red). By making this CloneBase + * code do the first level of co-variant return, it is this class which + * is victim of the Cobertura reporting, not the multiple subclasses + * (like Attribute, Document, Content, etc.). + */ + try { + return (CloneBase) super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException(String.format( + "Unable to clone class %s which should always support it.", + this.getClass().getName()), e); + } + } + +} diff --git a/core/src/java/org/jdom/Comment.java b/core/src/java/org/jdom/Comment.java new file mode 100644 index 0000000..51081ab --- /dev/null +++ b/core/src/java/org/jdom/Comment.java @@ -0,0 +1,167 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + + +import org.jdom.output.XMLOutputter2; + +/** + * An XML comment. Methods allow the user to get and set the text of the + * comment. + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public class Comment extends Content { + + /** + * JDOM2 Serialization. In this case, Comment is simple. + */ + private static final long serialVersionUID = 200L; + + /** Text of the Comment */ + protected String text; + + /** + * Default, no-args constructor for implementations to use if needed. + */ + protected Comment() { + super(CType.Comment); + } + + /** + * This creates the comment with the supplied text. + * + * @param text String content of comment. + */ + public Comment(String text) { + super(CType.Comment); + setText(text); + } + + + /** + * Returns the XPath 1.0 string value of this element, which is the + * text of this comment. + * + * @return the text of this comment + */ + @Override + public String getValue() { + return text; + } + + /** + * This returns the textual data within the Comment. + * + * @return String - text of comment. + */ + public String getText() { + return text; + } + + /** + * This will set the value of the Comment. + * + * @param text String text for comment. + * @return Comment - this Comment modified. + * @throws IllegalDataException if the given text is illegal for a + * Comment. + */ + public Comment setText(String text) { + String reason; + if ((reason = Verifier.checkCommentData(text)) != null) { + throw new IllegalDataException(text, "comment", reason); + } + + this.text = text; + return this; + } + + @Override + public Comment clone() { + return (Comment)super.clone(); + } + + @Override + public Comment detach() { + return (Comment)super.detach(); + } + + @Override + protected Comment setParent(Parent parent) { + return (Comment)super.setParent(parent); + } + + /** + * This returns a String representation of the + * Comment, suitable for debugging. If the XML + * representation of the Comment is desired, + * {@link XMLOutputter2#outputString(Comment)} + * should be used. + * + * @return String - information about the + * Comment + */ + @Override + public String toString() { + return new StringBuilder() + .append("[Comment: ") + .append(new XMLOutputter2().outputString(this)) + .append("]") + .toString(); + } + +} diff --git a/core/src/java/org/jdom/Content.java b/core/src/java/org/jdom/Content.java new file mode 100644 index 0000000..375e1e0 --- /dev/null +++ b/core/src/java/org/jdom/Content.java @@ -0,0 +1,295 @@ +/*-- + + Copyright (C) 2007-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.io.*; +import java.util.Collections; +import java.util.List; + +/** + * Superclass for JDOM objects which can be legal child content + * of {@link org.jdom.Parent} nodes. + * + * @see org.jdom.Comment + * @see org.jdom.DocType + * @see org.jdom.Element + * @see org.jdom.EntityRef + * @see org.jdom.Parent + * @see org.jdom.ProcessingInstruction + * @see org.jdom.Text + * + * @author Bradley S. Huffman + * @author Jason Hunter + * @author Rolf Lear + */ +public abstract class Content extends CloneBase + implements Serializable, NamespaceAware { + + /** + * JDOM2 Serialization. + */ + private static final long serialVersionUID = 200L; + + /** + * An enumeration useful for identifying content types without + * having to do instanceof type conditionals. + *

+ * instanceof is not a particularly safe way to test content + * in JDOM because CDATA extends Text ( a CDATA instance is an + * instanceof Text). + *

+ * This CType enumeration provides a direct and sure mechanism for + * identifying JDOM content. + *

+ * These can be used in switch-type statements too (which is much neater + * than chained if (instanceof) else if (instanceof) else .... expressions): + *

+ *

+	 *    switch(content.getCType()) {
+	 *        case Text :
+	 *             .....
+	 *             break; 
+	 *        case CDATA :
+	 *             .....
+	 *             break;
+	 *        ....
+	 *    }
+	 * 
+ * + * @author Rolf Lear + * @since JDOM2 + */ + public static enum CType { + /** Represents {@link Comment} content */ + Comment, + /** Represents {@link Element} content */ + Element, + /** Represents {@link ProcessingInstruction} content */ + ProcessingInstruction, + /** Represents {@link EntityRef} content */ + EntityRef, + /** Represents {@link Text} content */ + Text, + /** Represents {@link CDATA} content */ + CDATA, + /** Represents {@link DocType} content */ + DocType + } + + /** + * The parent {@link Parent} of this Content. + * Note that the field is not serialized, thus deserialized Content + * instances are 'detached' + */ + protected transient Parent parent = null; + /** + * The content type enumerate value for this Content + * @serialField This is an Enum, and cannot be null. + */ + protected final CType ctype; + + /** + * Create a new Content instance of a particular type. + * @param type The {@link CType} of this Content + */ + protected Content(CType type) { + ctype = type; + } + + + /** + * All content has an enumerated type expressing the type of content. + * This makes it possible to use switch-type statements on the content. + * @return A CType enumerated value representing this content. + */ + public final CType getCType() { + return ctype; + } + + /** + * Detaches this child from its parent or does nothing if the child + * has no parent. + *

+ * This method can be overridden by particular Content subclasses to return + * a specific type of Content (co-variant return type). All overriding + * subclasses must call super.detach(); + * + * @return this child detached + */ + public Content detach() { + if (parent != null) { + parent.removeContent(this); + } + return this; + } + + /** + * Return this child's parent, or null if this child is currently + * not attached. The parent can be either an {@link Element} + * or a {@link Document}. + *

+ * This method can be overridden by particular Content subclasses to return + * a specific type of Parent (co-variant return type). All overriding + * subclasses must call super.getParent(); + * + * @return this child's parent or null if none + */ + public Parent getParent() { + return parent; + } + + /** + * A convenience method that returns any parent element for this element, + * or null if the element is unattached or is a root element. This was the + * original behavior of getParent() in JDOM Beta 9 which began returning + * Parent in Beta 10. This method provides a convenient upgrade path for + * JDOM Beta 10 and 1.0 users. + * + * @return the containing Element or null if unattached or a root element + */ + final public Element getParentElement() { + Parent pnt = getParent(); + return (Element) ((pnt instanceof Element) ? pnt : null); + } + + /** + * Sets the parent of this Content. The caller is responsible for removing + * any pre-existing parentage. + *

+ * This method can be overridden by particular Content subclasses to return + * a specific type of Content (co-variant return type). All overriding + * subclasses must call super.setParent(Parent); + * + * @param parent new parent element + * @return the target element + */ + protected Content setParent(Parent parent) { + this.parent = parent; + return this; + } + + /** + * Return this child's owning document or null if the branch containing + * this child is currently not attached to a document. + * + * @return this child's owning document or null if none + */ + public Document getDocument() { + if (parent == null) return null; + return parent.getDocument(); + } + + + /** + * Returns the XPath 1.0 string value of this child. + * + * @return xpath string value of this child. + */ + public abstract String getValue(); + + @Override + public Content clone() { + Content c = (Content)super.clone(); + c.parent = null; + return c; + } + + /** + * This tests for equality of this Content object to the supplied object. + * Content items are considered equal only if they are referentially equal + * (i.e. the same object). User code may choose to compare objects + * based on their properties instead. + * + * @param ob Object to compare to. + * @return boolean - whether the Content is + * equal to the supplied Object. + */ + @Override + public final boolean equals(Object ob) { + return (ob == this); + } + + /** + * This returns the hash code for this Content item. + * + * @return int - hash code. + */ + @Override + public final int hashCode() { + return super.hashCode(); + } + + @Override + public List getNamespacesInScope() { + // Element class will override this method to do it differently. + Element emt = getParentElement(); + if (emt == null) { + return Collections.singletonList(Namespace.XML_NAMESPACE); + } + return emt.getNamespacesInScope(); + } + + @Override + public List getNamespacesIntroduced() { + return Collections.emptyList(); + } + + @Override + public List getNamespacesInherited() { + // Element class will override + return getNamespacesInScope(); + } + +} diff --git a/core/src/java/org/jdom/ContentList.java b/core/src/java/org/jdom/ContentList.java new file mode 100644 index 0000000..506f00d --- /dev/null +++ b/core/src/java/org/jdom/ContentList.java @@ -0,0 +1,1501 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +import org.jdom.filter.AbstractFilter; +import org.jdom.filter2.Filter; +import org.jdom.internal.ArrayCopy; + +/** + * A non-public list implementation holding only legal JDOM content, including + * content for Document or Element nodes. Users see this class as a simple List + * implementation. + * + * @see DocType + * @see CDATA + * @see Comment + * @see Element + * @see EntityRef + * @see ProcessingInstruction + * @see Text + * @author Alex Rosen + * @author Philippe Riand + * @author Bradley S. Huffman + * @author Rolf Lear + */ +final class ContentList extends AbstractList + implements RandomAccess { + + private static final int INITIAL_ARRAY_SIZE = 4; + + /** Our backing list */ + private Content elementData[] = null; + + /** The amount of valid content in elementData */ + private int size; + + /** + * Completely remove references to AbstractList.modCount because in + * ContentList it is confusing. As a consequence we also need to implement + * a custom ListIterator for ContentList so that we don't use any of the + * AbstractList iterators which use modCount.... so we have our own + * ConcurrentModification checking. + * + */ + private transient int sizeModCount = Integer.MIN_VALUE; + + /** + * modCount is used for concurrent modification, but dataModCount is used + * for refreshing filters. + */ + private transient int dataModiCount = Integer.MIN_VALUE; + + /** Document or Element this list belongs to */ + private final Parent parent; + + /** + * Force either a Document or Element parent + * + * @param parent + * the Element this ContentList belongs to. + */ + ContentList(final Parent parent) { + this.parent = parent; + } + + /** + * Package internal method to support building from sources that are 100% + * trusted. + * + * @param c + * content to add without any checks + */ + final void uncheckedAddContent(final Content c) { + c.parent = parent; + ensureCapacity(size + 1); + elementData[size++] = c; + incModCount(); + } + + /** + * In the FilterList and FilterList iterators it becomes confusing as to + * which modCount is being used. This formalizes the process, and using + * (set/get/inc)ModCount() is the only thing you should see in the remainder + * of this code. + * + * @param sizemod + * the value to set for the size-mod count. + * @param datamod + * the value to set for the data-mod count. + */ + private final void setModCount(final int sizemod, final int datamod) { + sizeModCount = sizemod; + dataModiCount = datamod; + } + + /** + * In the FilterList and FilterList iterators it becomes confusing as to + * which modCount is being used. This formalizes the process, and using + * (set/get/inc)ModCount() is the only thing you should see in the remainder + * of this code. + * + * @return mod the value. + */ + private final int getModCount() { + return sizeModCount; + } + + /** + * In the FilterList and FilterList iterators it becomes confusing as to + * which modCount is being used. This formalizes the process, and using + * (set/get/inc)ModCount() is the only thing you should see in the remainder + * of this code. + */ + private final void incModCount() { + // indicate there's a change to data + dataModiCount++; + // indicate there's a change to the size + sizeModCount++; + } + + private final void incDataModOnly() { + dataModiCount++; + } + + /** + * Get the modcount of data changes. + * @return the current data mode count. + */ + private final int getDataModCount() { + return dataModiCount; + } + + private final void checkIndex(final int index, final boolean excludes) { + final int max = excludes ? size - 1 : size; + + if (index < 0 || index > max) { + throw new IndexOutOfBoundsException("Index: " + index + + " Size: " + size); + } + + } + + private final void checkPreConditions(final Content child, final int index, + final boolean replace) { + if (child == null) { + throw new NullPointerException("Cannot add null object"); + } + + checkIndex(index, replace); + + if (child.getParent() != null) { + // the content to be added already has a parent. + final Parent p = child.getParent(); + if (p instanceof Document) { + throw new IllegalAddException((Element) child, + "The Content already has an existing parent document"); + } + throw new IllegalAddException( + "The Content already has an existing parent \"" + + ((Element) p).getQualifiedName() + "\""); + } + + if (child == parent) { + throw new IllegalAddException( + "The Element cannot be added to itself"); + } + + // Detect if we have and c.add(a) + if ((parent instanceof Element && child instanceof Element) && + ((Element) child).isAncestor((Element) parent)) { + throw new IllegalAddException( + "The Element cannot be added as a descendent of itself"); + } + + } + + /** + * Check and add the Content to this list at the given index. + * Inserts the specified object at the specified position in this list. + * Shifts the object currently at that position (if any) and any subsequent + * objects to the right (adds one to their indices). + * + * @param index + * index where to add Element + * @param child + * Content to add + */ + @Override + public void add(final int index, final Content child) { + // Confirm basic sanity of child. + checkPreConditions(child, index, false); + // Check to see whether this parent believes it can contain this content + parent.canContainContent(child, index, false); + + child.setParent(parent); + + ensureCapacity(size + 1); + if (index == size) { + elementData[size++] = child; + } else { + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = child; + size++; + } + // Successful add's increment the AbstractList's modCount + incModCount(); + } + + /** + * Add the specified collection to the end of this list. + * + * @param collection + * The collection to add to the list. + * @return true if the list was modified as a result of the + * add. + */ + @Override + public boolean addAll(final Collection collection) { + return addAll(size, collection); + } + + /** + * Inserts the specified collection at the specified position in this list. + * Shifts the object currently at that position (if any) and any subsequent + * objects to the right (adds one to their indices). + * + * @param index + * The offset to start adding the data in the collection + * @param collection + * The collection to insert into the list. + * @return true if the list was modified as a result of the + * add. throws IndexOutOfBoundsException if index < 0 || index > + * size() + */ + @Override + public boolean addAll(final int index, + final Collection collection) { + if ((collection == null)) { + throw new NullPointerException( + "Can not add a null collection to the ContentList"); + } + + checkIndex(index, false); + + if (collection.isEmpty()) { + // some collections are expensive to get the size of. + // use isEmpty(). + return false; + } + final int addcnt = collection.size(); + if (addcnt == 1) { + // quick check for single-add. + add(index, collection.iterator().next()); + return true; + } + + ensureCapacity(size() + addcnt); + + final int tmpmodcount = getModCount(); + final int tmpdmc = getDataModCount(); + boolean ok = false; + + int count = 0; + + try { + for (Content c : collection) { + add(index + count, c); + count++; + } + ok = true; + } finally { + if (!ok) { + // something failed... remove all added content + while (--count >= 0) { + remove(index + count); + } + // restore the mod-counts. + setModCount(tmpmodcount, tmpdmc); + } + } + + return true; + } + + /** + * Clear the current list. + */ + @Override + public void clear() { + if (elementData != null) { + for (int i = 0; i < size; i++) { + Content obj = elementData[i]; + removeParent(obj); + } + elementData = null; + size = 0; + } + incModCount(); + } + + /** + * Clear the current list and set it to the contents of the + * Collection. object. + * + * @param collection + * The collection to use. + */ + void clearAndSet(final Collection collection) { + if (collection == null || collection.isEmpty()) { + clear(); + return; + } + + // keep a backup in case we need to roll-back... + final Content[] old = elementData; + final int oldSize = size; + final int oldModCount = getModCount(); + final int oldDataModCount = getDataModCount(); + + // clear the current system + // we need to detach before we add so that we don't run in to a problem + // where a content in the to-add list is one that we are 'clearing' + // first. + while (size > 0) { + old[--size].setParent(null); + } + size = 0; + elementData = null; + + boolean ok = false; + try { + addAll(0, collection); + ok = true; + } finally { + if (!ok) { + // we have an exception pending.... + // restore the old system. + // we do not need to worry about the added content + // because the failed addAll will clear it up. + // re-attach the old stuff + elementData = old; + while (size < oldSize) { + elementData[size++].setParent(parent); + } + setModCount(oldModCount, oldDataModCount); + } + } + + } + + /** + * Increases the capacity of this ContentList instance, if + * necessary, to ensure that it can hold at least the number of items + * specified by the minimum capacity argument. + * + * @param minCapacity + * the desired minimum capacity. + */ + void ensureCapacity(final int minCapacity) { + if (elementData == null) { + elementData = new Content[Math.max(minCapacity, INITIAL_ARRAY_SIZE)]; + return; + } else if (minCapacity < elementData.length) { + return; + } + // use algorithm Wilf suggests which is essentially the same + // as algorithm as ArrayList.ensureCapacity.... + // typically the minCapacity is only slightly larger than + // the current capacity.... so grow from the current capacity + // with a double-check. + final int newcap = ((size * 3) / 2) + 1; + elementData = ArrayCopy.copyOf(elementData, + (newcap < minCapacity ? minCapacity : newcap)); + } + + /** + * Return the object at the specified offset. + * + * @param index + * The offset of the object. + * @return The Object which was returned. + */ + @Override + public Content get(final int index) { + checkIndex(index, true); + return elementData[index]; + } + + /** + * Return a view of this list based on the given filter. + * + * @param + * The Generic type of the content as set by the Filter. + * @param filter + * Filter for this view. + * @return a list representing the rules of the Filter. + */ + List getView(final org.jdom.filter2.Filter filter) { + return new FilterList(filter); + } + + /** + * Return a view of this list based on the given filter. + * + * @param + * The Generic type of the content as set by the Filter. + * @param filter + * Filter for this view. + * @return a list representing the rules of the Filter. + */ + List getView(final org.jdom.filter.Filter filter) { + return new FilterList(AbstractFilter.toFilter2(filter)); + } + + /** + * Return the index of the first Element in the list. If the parent is a + * Document then the element is the root element. If the list + * contains no Elements, it returns -1. + * + * @return index of first element, or -1 if one doesn't exist + */ + int indexOfFirstElement() { + if (elementData != null) { + for (int i = 0; i < size; i++) { + if (elementData[i] instanceof Element) { + return i; + } + } + } + return -1; + } + + /** + * Return the index of the DocType element in the list. If the list contains + * no DocType, it returns -1. + * + * @return index of the DocType, or -1 if it doesn't exist + */ + int indexOfDocType() { + if (elementData != null) { + for (int i = 0; i < size; i++) { + if (elementData[i] instanceof DocType) { + return i; + } + } + } + return -1; + } + + /** + * Remove the object at the specified offset. + * + * @param index + * The offset of the object. + * @return The Object which was removed. + */ + @Override + public Content remove(final int index) { + checkIndex(index, true); + + final Content old = elementData[index]; + removeParent(old); + System.arraycopy(elementData, index + 1, elementData, index, size - index - 1); + elementData[--size] = null; // Let gc do its work + incModCount(); + return old; + } + + /** Remove the parent of a Object */ + private static void removeParent(final Content c) { + c.setParent(null); + } + + /** + * Set the object at the specified location to the supplied object. + * + * @param index + * The location to set the value to. + * @param child + * The location to set the value to. + * @return The object which was replaced. throws IndexOutOfBoundsException + * if index < 0 || index >= size() + */ + @Override + public Content set(final int index, final Content child) { + // Confirm basic sanity of child. + checkPreConditions(child, index, true); + + // Ensure the detail checks out OK too. + parent.canContainContent(child, index, true); + + /* + * Do a special case of set() where we don't do a remove() then add() + * because that affects the modCount. We want to do a true set(). See + * issue #15 + */ + + final Content old = elementData[index]; + removeParent(old); + child.setParent(parent); + elementData[index] = child; + // for set method we increment dataModCount, but not modCount + // set does not change the structure of the List (size()) + incDataModOnly(); + return old; + } + + /** + * Return the number of items in this list + * + * @return The number of items in this list. + */ + @Override + public int size() { + return size; + } + + @Override + public Iterator iterator() { + return new CLIterator(); + } + + @Override + public ListIterator listIterator() { + return new CLListIterator(0); + } + + @Override + public ListIterator listIterator(final int start) { + return new CLListIterator(start); + } + + /** + * Return this list as a String + * + * @return The String representation of this list. + */ + @Override + public String toString() { + return super.toString(); + } + + private void sortInPlace(final int[] indexes) { + // the indexes are a discrete set of values that have no duplicates, + // and describe the relative order of each of them. + // as a result, we can do some tricks.... + final int[] unsorted = ArrayCopy.copyOf(indexes, indexes.length); + Arrays.sort(unsorted); + final Content[] usc = new Content[unsorted.length]; + for (int i = 0; i < usc.length; i++) { + usc[i] = elementData[indexes[i]]; + } + // usc contains the content in their pre-sorted order.... + for (int i = 0; i < indexes.length; i ++) { + elementData[unsorted[i]] = usc[i]; + } + } + + /** + * Unlike the Arrays.binarySearch, this method never expects an + * "already exists" condition, we only ever add, thus there will never + * be a negative insertion-point. + * @param indexes THe pointers to search within + * @param len The number of pointers to search within + * @param val The pointer we are checking for. + * @param comp The Comparator to compare with + * @return the insertion point. + */ + private final int binarySearch(final int[] indexes, final int len, + final int val, final Comparator comp) { + int left = 0, mid = 0, right = len - 1, cmp = 0; + final Content base = elementData[val]; + while (left <= right) { + mid = (left + right) >>> 1; + cmp = comp.compare(base, elementData[indexes[mid]]); + if (cmp == 0) { + while (cmp == 0 && mid < right && comp.compare( + base, elementData[indexes[mid + 1]]) == 0) { + mid++; + } + return mid + 1; + } else if (cmp < 0) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; + } + + /** + * Sorts this list using the supplied Comparator to compare elements. + * + * @param comp - the Comparator used to compare list elements. A null value indicates that the elements' natural ordering should be used + * @Since 2.0.6 + */ + // @Override - only in Java8 + public final void sort(final Comparator comp) { + + if (comp == null) { + // sort by the 'natural order', which, there is none. + // options, throw exception, or let the current-order represent the natural order. + // do nothing is the better alternative. + return; + } + + final int sz = size; + int[] indexes = new int[sz]; + for (int i = 0 ; i < sz; i++) { + final int ip = binarySearch(indexes, i, i, comp); + if (ip < i) { + System.arraycopy(indexes, ip, indexes, ip+1, i - ip); + } + indexes[ip] = i; + } + sortInPlace(indexes); + } + + /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ + /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ + /** + * A fast implementation of Iterator. + *

+ * It is fast because it is tailored to the ContentList, and not the + * flexible implementation used by AbstractList. It needs to be fast because + * iterator() is used extensively in the for-each type loop. + * + * @author Rolf Lear + */ + private final class CLIterator implements Iterator { + private int expect = -1; + private int cursor = 0; + private boolean canremove = false; + + private CLIterator() { + expect = getModCount(); + } + + @Override + public boolean hasNext() { + return cursor < size; + } + + @Override + public Content next() { + if (getModCount() != expect) { + throw new ConcurrentModificationException("ContentList was " + + "modified outside of this Iterator"); + } + if (cursor >= size) { + throw new NoSuchElementException("Iterated beyond the end of " + + "the ContentList."); + } + canremove = true; + return elementData[cursor++]; + } + + @Override + public void remove() { + if (getModCount() != expect) { + throw new ConcurrentModificationException("ContentList was " + + "modified outside of this Iterator"); + } + if (!canremove) { + throw new IllegalStateException("Can only remove() content " + + "after a call to next()"); + } + canremove = false; + ContentList.this.remove(--cursor); + expect = getModCount(); + } + + } + + /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ + /* * * * * * * * * * * * * ContentListIterator * * * * * * * * * * * * * * * */ + /** + * A fast implementation of Iterator. + *

+ * It is fast because it is tailored to the ContentList, and not the + * flexible implementation used by AbstractList. It needs to be fast because + * iterator() is used extensively in the for-each type loop. + * + * @author Rolf Lear + */ + private final class CLListIterator implements ListIterator { + /** Whether this iterator is in forward or reverse. */ + private boolean forward = false; + /** Whether a call to remove() is valid */ + private boolean canremove = false; + /** Whether a call to set() is valid */ + private boolean canset = false; + + /** Expected modCount in our backing list */ + private int expectedmod = -1; + + private int cursor = -1; + + /** + * Default constructor + * + * @param flist + * The FilterList over which we will iterate. + * @param start + * where in the FilterList to start iteration. + */ + CLListIterator(final int start) { + expectedmod = getModCount(); + // always start list iterators in backward mode .... + // it makes sense... really. + forward = false; + + checkIndex(start, false); + + cursor = start; + } + + private void checkConcurrent() { + if (expectedmod != getModCount()) { + throw new ConcurrentModificationException("The ContentList " + + "supporting this iterator has been modified by" + + "something other than this Iterator."); + } + } + + /** + * Returns true if this list iterator has a next element. + */ + @Override + public boolean hasNext() { + return (forward ? cursor + 1 : cursor) < size; + } + + /** + * Returns true if this list iterator has more elements + * when traversing the list in the reverse direction. + */ + @Override + public boolean hasPrevious() { + return (forward ? cursor : cursor - 1) >= 0; + } + + /** + * Returns the index of the element that would be returned by a + * subsequent call to next. + */ + @Override + public int nextIndex() { + return forward ? cursor + 1 : cursor; + } + + /** + * Returns the index of the element that would be returned by a + * subsequent call to previous. (Returns -1 if the list + * iterator is at the beginning of the list.) + */ + @Override + public int previousIndex() { + return forward ? cursor : cursor - 1; + } + + /** + * Returns the next element in the list. + */ + @Override + public Content next() { + checkConcurrent(); + final int next = forward ? cursor + 1 : cursor; + + if (next >= size) { + throw new NoSuchElementException("next() is beyond the end of the Iterator"); + } + + cursor = next; + forward = true; + canremove = true; + canset = true; + return elementData[cursor]; + } + + /** + * Returns the previous element in the list. + */ + @Override + public Content previous() { + checkConcurrent(); + final int prev = forward ? cursor : cursor - 1; + + if (prev < 0) { + throw new NoSuchElementException("previous() is beyond the beginning of the Iterator"); + } + + cursor = prev; + forward = false; + canremove = true; + canset = true; + return elementData[cursor]; + } + + /** + * Inserts the specified element into the list . + */ + @Override + public void add(final Content obj) { + checkConcurrent(); + // always add before what would normally be returned by next(); + final int next = forward ? cursor + 1 : cursor; + + ContentList.this.add(next, obj); + + expectedmod = getModCount(); + + canremove = canset = false; + + // a call to next() should be unaffected, so, whatever was going to + // be next will still be next, remember, what was going to be next + // has been shifted 'right' by our insert. + // we ensure this by setting the cursor to next(), and making it + // forward + cursor = next; + forward = true; + } + + /** + * Removes from the list the last element that was returned by the last + * call to next or previous. + */ + @Override + public void remove() { + checkConcurrent(); + if (!canremove) + throw new IllegalStateException("Can not remove an " + + "element unless either next() or previous() has been called " + + "since the last remove()"); + // we are removing the last entry returned by either next() or + // previous(). + // the idea is to remove it, and pretend that we used to be at the + // entry that happened *after* the removed entry. + // so, get what would be the next entry (set at tmpcursor). + // so call nextIndex to set tmpcursor to what would come after. + ContentList.this.remove(cursor); + forward = false; + expectedmod = getModCount(); + + canremove = false; + canset = false; + } + + /** + * Replaces the last element returned by next or + * previous with the specified element. + */ + @Override + public void set(final Content obj) { + checkConcurrent(); + if (!canset) { + throw new IllegalStateException("Can not set an element " + + "unless either next() or previous() has been called since the " + + "last remove() or set()"); + } + + ContentList.this.set(cursor, obj); + expectedmod = getModCount(); + + } + + } + + /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */ + /* * * * * * * * * * * * * FilterList * * * * * * * * * * * * * * * */ + + /** + * FilterList represents legal JDOM content, including content + * for Documents or Elements. + *

+ * FilterList represents a dynamic view of the backing ContentList, changes + * to the backing list are reflected in the FilterList, and visa-versa. + * + * @param + * The Generic type of content accepted by the underlying Filter. + */ + + class FilterList extends AbstractList { + + // The filter to apply + final Filter filter; + // correlate the position in the filtered list to the index in the + // backing ContentList. + int[] backingpos = new int[size + INITIAL_ARRAY_SIZE]; + int backingsize = 0; + // track data modifications in the backing ContentList. + int xdata = -1; + + /** + * Create a new instance of the FilterList with the specified Filter. + * + * @param filter + * The underlying Filter to use for filtering the content. + */ + FilterList(final Filter filter) { + this.filter = filter; + } + + /** + * Returns true if there is no content in this FilterList. + * @return true if there is no content in this FilterList + */ + @Override + public boolean isEmpty() { + // More efficient implementation than default size() == 0 + // we use resync() to accomplish the task. If there is an + // element 0 in this FilterList, then it is not empty! + // we may already have resync'd 0, which will be a fast return then, + // or, if we have not resync'd 0, then we only have to filter up to + // the first matching element to get a result (or the whole list + // if isEmpty() is true). + return resync(0) == size; + } + + /** + * Synchronise our view to the backing list. Only synchronise the first + * index view elements. For want of a better word, we'll + * call this a 'Lazy' implementation. + * + * @param index + * how much we want to sync. Set to -1 to synchronise everything. + * @return the index in the backing array of the index'th match. + * or the backing data size if there is no match for the index. + */ + private final int resync(final int index) { + if (xdata != getDataModCount()) { + // The underlying list was modified somehow... + // we need to invalidate our research... + xdata = getDataModCount(); + backingsize = 0; + if (size >= backingpos.length) { + backingpos = new int[size + 1]; + } + } + + if (index >= 0 && index < backingsize) { + // we have already indexed far enough... + // return the backing index. + return backingpos[index]; + } + + // the index in the backing list of the next value to check. + int bpi = 0; + if (backingsize > 0) { + bpi = backingpos[backingsize - 1] + 1; + } + + while (bpi < size) { + final F gotit = filter.filter(elementData[bpi]); + if (gotit != null) { + backingpos[backingsize] = bpi; + if (backingsize++ == index) { + return bpi; + } + } + bpi++; + } + return size; + } + + /** + * Inserts the specified object at the specified position in this list. + * Shifts the object currently at that position (if any) and any + * subsequent objects to the right (adds one to their indices). + * + * @param index + * The location to set the value to. + * @param obj + * The object to insert into the list. throws + * IndexOutOfBoundsException if index < 0 || index > size() + */ + @Override + public void add(final int index, final Content obj) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + int adj = resync(index); + if (adj == size && index > size()) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + if (filter.matches(obj)) { + ContentList.this.add(adj, obj); + + // we can optimise the laziness now by doing a partial reset on + // the backing list... invalidate everything *after* the added + // content + if (backingpos.length <= size) { + backingpos = ArrayCopy.copyOf(backingpos, backingpos.length + 1); + } + backingpos[index] = adj; + backingsize = index + 1; + xdata = getDataModCount(); + + } else { + throw new IllegalAddException("Filter won't allow the " + + obj.getClass().getName() + + " '" + obj + "' to be added to the list"); + } + } + + @Override + public boolean addAll(final int index, + final Collection collection) { + if (collection == null) { + throw new NullPointerException("Cannot add a null collection"); + } + + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + + final int adj = resync(index); + if (adj == size && index > size()) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + + final int addcnt = collection.size(); + if (addcnt == 0) { + return false; + } + + ContentList.this.ensureCapacity(ContentList.this.size() + addcnt); + + final int tmpmodcount = getModCount(); + final int tmpdmc = getDataModCount(); + boolean ok = false; + + int count = 0; + + try { + for (Content c : collection) { + if (c == null) { + throw new NullPointerException( + "Cannot add null content"); + } + if (filter.matches(c)) { + ContentList.this.add(adj + count, c); + // we can optimise the laziness now by doing a partial + // reset on + // the backing list... invalidate everything *after* the + // added + // content + if (backingpos.length <= size) { + backingpos = ArrayCopy.copyOf(backingpos, backingpos.length + addcnt); + } + backingpos[index + count] = adj + count; + backingsize = index + count + 1; + xdata = getDataModCount(); + + count++; + } else { + throw new IllegalAddException("Filter won't allow the " + + c.getClass().getName() + + " '" + c + "' to be added to the list"); + + } + } + ok = true; + } finally { + if (!ok) { + // something failed... remove all added content + while (--count >= 0) { + ContentList.this.remove(adj + count); + } + // restore the mod-counts. + setModCount(tmpmodcount, tmpdmc); + // reset the cache... will need to redo some work on another + // call maybe.... + backingsize = index; + xdata = tmpmodcount; + } + } + + return true; + } + + /** + * Return the object at the specified offset. + * + * @param index + * The offset of the object. + * @return The Object which was returned. + */ + @Override + public F get(final int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + final int adj = resync(index); + if (adj == size) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + return filter.filter(ContentList.this.get(adj)); + } + + @Override + public Iterator iterator() { + return new FilterListIterator(this, 0); + } + + @Override + public ListIterator listIterator() { + return new FilterListIterator(this, 0); + } + + @Override + public ListIterator listIterator(final int index) { + return new FilterListIterator(this, index); + } + + /** + * Remove the object at the specified offset. + * + * @param index + * The offset of the object. + * @return The Object which was removed. + */ + @Override + public F remove(final int index) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + final int adj = resync(index); + if (adj == size) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + final Content oldc = ContentList.this.remove(adj); + // optimise the backing cache. + backingsize = index; + xdata = getDataModCount(); + // use Filter to ensure the cast is right. + return filter.filter(oldc); + } + + /** + * Set the object at the specified location to the supplied object. + * + * @param index + * The location to set the value to. + * @param obj + * The location to set the value to. + * @return The object which was replaced. throws + * IndexOutOfBoundsException if index < 0 || index >= size() + */ + @Override + public F set(final int index, final F obj) { + if (index < 0) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + final int adj = resync(index); + if (adj == size) { + throw new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + final F ins = filter.filter(obj); + if (ins != null) { + final F oldc = filter.filter(ContentList.this.set(adj, ins)); + // optimize the backing.... + xdata = getDataModCount(); + return oldc; + } + throw new IllegalAddException("Filter won't allow index " + + index + " to be set to " + + (obj.getClass()).getName()); + } + + /** + * Return the number of items in this list + * + * @return The number of items in this list. + */ + @Override + public int size() { + resync(-1); + return backingsize; + } + + /** + * Unlike the Arrays.binarySearch, this method never expects an + * "already exists" condition, we only ever add, thus there will never + * be a negative insertion-point. + * @param indexes THe pointers to search within + * @param len The number of pointers to search within + * @param val The pointer we are checking for. + * @param comp The Comparator to compare with + * @return the insertion point. + */ + @SuppressWarnings("unchecked") + private final int fbinarySearch(final int[] indexes, final int len, + final int val, final Comparator comp) { + int left = 0, mid = 0, right = len - 1, cmp = 0; + final F base = (F)elementData[backingpos[val]]; + while (left <= right) { + mid = (left + right) >>> 1; + cmp = comp.compare(base, (F)elementData[indexes[mid]]); + if (cmp == 0) { + while (cmp == 0 && mid < right && comp.compare( + base, (F)elementData[indexes[mid + 1]]) == 0) { + mid++; + } + return mid + 1; + } else if (cmp < 0) { + right = mid - 1; + } else { + left = mid + 1; + } + } + return left; + } + + + /** + * Sorts this list using the supplied Comparator to compare elements. + * + * @param comp - the Comparator used to compare list elements. A null value indicates that the elements' natural ordering should be used + * @Since 2.0.6 + */ + //Not till Java8 @Override + public final void sort(final Comparator comp) { + // this size() forces a full scan/update of the list. + if (comp == null) { + // sort by the 'natural order', which, there is none. + // options, throw exception, or let the current-order represent the natural order. + // do nothing is the better alternative. + return; + } + final int sz = size(); + final int[] indexes = new int[sz]; + for (int i = 0 ; i < sz; i++) { + final int ip = fbinarySearch(indexes, i, i, comp); + if (ip < i) { + System.arraycopy(indexes, ip, indexes, ip+1, i - ip); + } + indexes[ip] = backingpos[i]; + } + sortInPlace(indexes); + } + + } + + /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */ + /* * * * * * * * * * * * * FilterListIterator * * * * * * * * * * * */ + + final class FilterListIterator implements ListIterator { + + /** The Filter that applies */ + private final FilterList filterlist; + + /** Whether this iterator is in forward or reverse. */ + private boolean forward = false; + /** Whether a call to remove() is valid */ + private boolean canremove = false; + /** Whether a call to set() is valid */ + private boolean canset = false; + + /** Expected modCount in our backing list */ + private int expectedmod = -1; + + private int cursor = -1; + + /** + * Default constructor + * + * @param flist + * The FilterList over which we will iterate. + * @param start + * where in the FilterList to start iteration. + */ + FilterListIterator(final FilterList flist, final int start) { + filterlist = flist; + expectedmod = getModCount(); + // always start list iterators in backward mode .... + // it makes sense... really. + forward = false; + + if (start < 0) { + throw new IndexOutOfBoundsException("Index: " + start + " Size: " + filterlist.size()); + } + + final int adj = filterlist.resync(start); + + if (adj == size && start > filterlist.size()) { + // the start point is after the end of the list. + // it is only allowed to be the same as size(), no larger. + throw new IndexOutOfBoundsException("Index: " + start + " Size: " + filterlist.size()); + } + + cursor = start; + } + + private void checkConcurrent() { + if (expectedmod != getModCount()) { + throw new ConcurrentModificationException("The ContentList " + + "supporting the FilterList this iterator is " + + "processing has been modified by something other " + + "than this Iterator."); + } + } + + /** + * Returns true if this list iterator has a next element. + */ + @Override + public boolean hasNext() { + return filterlist.resync(forward ? cursor + 1 : cursor) < size; + } + + /** + * Returns true if this list iterator has more elements + * when traversing the list in the reverse direction. + */ + @Override + public boolean hasPrevious() { + return (forward ? cursor : cursor - 1) >= 0; + } + + /** + * Returns the index of the element that would be returned by a + * subsequent call to next. + */ + @Override + public int nextIndex() { + return forward ? cursor + 1 : cursor; + } + + /** + * Returns the index of the element that would be returned by a + * subsequent call to previous. (Returns -1 if the list + * iterator is at the beginning of the list.) + */ + @Override + public int previousIndex() { + return forward ? cursor : cursor - 1; + } + + /** + * Returns the next element in the list. + */ + @Override + public F next() { + checkConcurrent(); + final int next = forward ? cursor + 1 : cursor; + + if (filterlist.resync(next) >= size) { + throw new NoSuchElementException("next() is beyond the end of the Iterator"); + } + + cursor = next; + forward = true; + canremove = true; + canset = true; + return filterlist.get(cursor); + } + + /** + * Returns the previous element in the list. + */ + @Override + public F previous() { + checkConcurrent(); + final int prev = forward ? cursor : cursor - 1; + + if (prev < 0) { + throw new NoSuchElementException("previous() is beyond the beginning of the Iterator"); + } + + cursor = prev; + forward = false; + canremove = true; + canset = true; + return filterlist.get(cursor); + } + + /** + * Inserts the specified element into the list . + */ + @Override + public void add(final Content obj) { + checkConcurrent(); + // always add before what would normally be returned by next(); + final int next = forward ? cursor + 1 : cursor; + + filterlist.add(next, obj); + + expectedmod = getModCount(); + + canremove = canset = false; + + // a call to next() should be unaffected, so, whatever was going to + // be next will still be next, remember, what was going to be next + // has been shifted 'right' by our insert. + // we ensure this by setting the cursor to next(), and making it + // forward + cursor = next; + forward = true; + } + + /** + * Removes from the list the last element that was returned by the last + * call to next or previous. + */ + @Override + public void remove() { + checkConcurrent(); + if (!canremove) + throw new IllegalStateException("Can not remove an " + + "element unless either next() or previous() has been called " + + "since the last remove()"); + // we are removing the last entry returned by either next() or + // previous(). + // the idea is to remove it, and pretend that we used to be at the + // entry that happened *after* the removed entry. + // so, get what would be the next entry (set at tmpcursor). + // so call nextIndex to set tmpcursor to what would come after. + filterlist.remove(cursor); + forward = false; + expectedmod = getModCount(); + + canremove = false; + canset = false; + } + + /** + * Replaces the last element returned by next or + * previous with the specified element. + */ + @Override + public void set(final F obj) { + checkConcurrent(); + if (!canset) { + throw new IllegalStateException("Can not set an element " + + "unless either next() or previous() has been called since the " + + "last remove() or set()"); + } + + filterlist.set(cursor, obj); + expectedmod = getModCount(); + + } + + } + +} diff --git a/core/src/java/org/jdom/DataConversionException.java b/core/src/java/org/jdom/DataConversionException.java new file mode 100644 index 0000000..e9ffd6c --- /dev/null +++ b/core/src/java/org/jdom/DataConversionException.java @@ -0,0 +1,86 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * Thrown when a data conversion from a string to value type fails, such as + * can happen with the {@link Attribute} convenience getter functions. + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public class DataConversionException extends JDOMException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * Constructs an exception where the named construct couldn't be converted + * to the named data type. + * + * @param name name of the construct whose value failed conversion + * @param dataType type the conversion was attempting to create + */ + public DataConversionException(String name, String dataType) { + super(new StringBuilder() + .append("The XML construct ") + .append(name) + .append(" could not be converted to a ") + .append(dataType) + .toString()); + } +} diff --git a/core/src/java/org/jdom/DefaultJDOMFactory.java b/core/src/java/org/jdom/DefaultJDOMFactory.java new file mode 100644 index 0000000..794ad2b --- /dev/null +++ b/core/src/java/org/jdom/DefaultJDOMFactory.java @@ -0,0 +1,330 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +/** + * Creates the standard top-level JDOM classes (Element, Document, Comment, + * etc). A subclass of this factory might construct custom classes. + * + * @author Ken Rune Holland + * @author Phil Nelson + * @author Bradley S. Huffman + * @author Rolf Lear + */ +public class DefaultJDOMFactory implements JDOMFactory { + + /** + * Creates a new DefaultJDOMFactory instance. + */ + public DefaultJDOMFactory() { + // do nothing special + } + + // Allow Javadocs to inherit from JDOMFactory + + @Override + public Attribute attribute(String name, String value, Namespace namespace) { + return new Attribute(name, value, namespace); + } + + @Override + @Deprecated + public Attribute attribute(String name, String value, int type, + Namespace namespace) { + return new Attribute(name, value, AttributeType.byIndex(type), + namespace); + } + + @Override + public Attribute attribute(String name, String value, AttributeType type, + Namespace namespace) { + return new Attribute(name, value, type, namespace); + } + + @Override + public Attribute attribute(String name, String value) { + return new Attribute(name, value); + } + + @Override + @Deprecated + public Attribute attribute(String name, String value, int type) { + return new Attribute(name, value, type); + } + + @Override + public Attribute attribute(String name, String value, AttributeType type) { + return new Attribute(name, value, type); + } + + @Override + public final CDATA cdata(String str) { + return cdata(-1, -1, str); + } + + @Override + public CDATA cdata(final int line, final int col, final String text) { + return new CDATA(text); + } + + @Override + public final Text text(String str) { + return text(-1, -1, str); + } + + @Override + public Text text(final int line, final int col, final String text) { + return new Text(text); + } + + @Override + public final Comment comment(String text) { + return comment(-1, -1, text); + } + + @Override + public Comment comment(final int line, final int col, final String text) { + return new Comment(text); + } + + @Override + public final DocType docType(String elementName, String publicID, String systemID) { + return docType(-1, -1, elementName, publicID, systemID); + } + + @Override + public DocType docType(final int line, final int col, + final String elementName, String publicID, String systemID) { + return new DocType(elementName, publicID, systemID); + } + + @Override + public final DocType docType(String elementName, String systemID) { + return docType(-1, -1, elementName, systemID); + } + + @Override + public DocType docType(final int line, final int col, + final String elementName, String systemID) { + return new DocType(elementName, systemID); + } + + @Override + public final DocType docType(String elementName) { + return docType(-1, -1, elementName); + } + + @Override + public DocType docType(final int line, final int col, + final String elementName) { + return new DocType(elementName); + } + + @Override + public Document document(Element rootElement, DocType docType) { + return new Document(rootElement, docType); + } + + @Override + public Document document(Element rootElement, DocType docType, + String baseURI) { + return new Document(rootElement, docType, baseURI); + } + + @Override + public Document document(Element rootElement) { + return new Document(rootElement); + } + + @Override + public Element element(String name, Namespace namespace) { + return element(-1, -1, name, namespace); + } + + @Override + public Element element(final int line, final int col, final String name, + Namespace namespace) { + return new Element(name, namespace); + } + + @Override + public Element element(String name) { + return element(-1, -1, name); + } + + @Override + public Element element(final int line, final int col, final String name) { + return new Element(name); + } + + @Override + public Element element(String name, String uri) { + return element(-1, -1, name, uri); + } + + @Override + public Element element(final int line, final int col, final String name, + String uri) { + return new Element(name, uri); + } + + @Override + public Element element(String name, String prefix, String uri) { + return element(-1, -1, name, prefix, uri); + } + + @Override + public Element element(final int line, final int col, final String name, + String prefix, String uri) { + return new Element(name, prefix, uri); + } + + @Override + public final ProcessingInstruction processingInstruction(String target) { + return processingInstruction(-1, -1, target); + } + + @Override + public ProcessingInstruction processingInstruction(final int line, + final int col, final String target) { + return new ProcessingInstruction(target); + } + + @Override + public final ProcessingInstruction processingInstruction(String target, + Map data) { + return processingInstruction(-1, -1, target, data); + } + + @Override + public ProcessingInstruction processingInstruction(final int line, + final int col, final String target, Map data) { + return new ProcessingInstruction(target, data); + } + + @Override + public final ProcessingInstruction processingInstruction(String target, + String data) { + return processingInstruction(-1, -1, target, data); + } + + @Override + public ProcessingInstruction processingInstruction(final int line, + final int col, final String target, String data) { + return new ProcessingInstruction(target, data); + } + + @Override + public final EntityRef entityRef(String name) { + return entityRef(-1, -1, name); + } + + @Override + public EntityRef entityRef(final int line, final int col, final String name) { + return new EntityRef(name); + } + + @Override + public final EntityRef entityRef(String name, String publicID, String systemID) { + return entityRef(-1, -1, name, publicID, systemID); + } + + @Override + public EntityRef entityRef(final int line, final int col, + final String name, String publicID, String systemID) { + return new EntityRef(name, publicID, systemID); + } + + @Override + public final EntityRef entityRef(String name, String systemID) { + return entityRef(-1, -1, name, systemID); + } + + @Override + public EntityRef entityRef(final int line, final int col, + final String name, String systemID) { + return new EntityRef(name, systemID); + } + + // ===================================================================== + // List manipulation + // ===================================================================== + + @Override + public void addContent(Parent parent, Content child) { + if (parent instanceof Document) { + ((Document) parent).addContent(child); + } else { + ((Element) parent).addContent(child); + } + } + + @Override + public void setAttribute(Element parent, Attribute a) { + parent.setAttribute(a); + } + + @Override + public void addNamespaceDeclaration(Element parent, Namespace additional) { + parent.addNamespaceDeclaration(additional); + } + + @Override + public void setRoot(Document doc, Element root) { + doc.setRootElement(root); + } +} diff --git a/core/src/java/org/jdom/DescendantIterator.java b/core/src/java/org/jdom/DescendantIterator.java new file mode 100644 index 0000000..60426f6 --- /dev/null +++ b/core/src/java/org/jdom/DescendantIterator.java @@ -0,0 +1,208 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +import org.jdom.internal.ArrayCopy; +import org.jdom.util.IteratorIterable; + +/** + * Traverse all a parent's descendants (all children at any level below + * the parent - excludes the parent itself). + * + * @author Bradley S. Huffman + * @author Jason Hunter + * @author Rolf Lear + */ +final class DescendantIterator implements IteratorIterable { + + /** Needed to be Iterable! */ + private final Parent parent; + + /* + * Note, we use an Array of Object here, even through + * List> would look neater, etc. + * Fact is, for 'hamlet', using a list for the stack takes about + * twice as long as using the Object[] array. + */ + private Object[] stack = new Object[16]; + private int ssize = 0; + + /** The iterator that supplied to most recent next() */ + private Iterator current = null; + /** The iterator going down the tree, null unless next() returned Parent */ + private Iterator descending = null; + /** The iterator going up the tree, null unless next() returned dead-end */ + private Iterator ascending = null; + /** what it says... */ + private boolean hasnext = true; + + /** + * Iterator for the descendants of the supplied object. + * + * @param parent document or element whose descendants will be iterated + */ + DescendantIterator(Parent parent) { + this.parent = parent; + // can trust that parent is not null, DescendantIterator is package-private. + current = parent.getContent().iterator(); + hasnext = current.hasNext(); + } + + @Override + public DescendantIterator iterator() { + // Implement the Iterable stuff. + return new DescendantIterator(parent); + } + + /** + * Returns true if the iteration has more {@link Content} descendants. + * + * @return true is the iterator has more descendants + */ + @Override + public boolean hasNext() { + return hasnext; + } + + /** + * Returns the next {@link Content} descendant. + * + * @return the next descendant + */ + @Override + public Content next() { + // set the 'current' if it needs changing. + if (descending != null) { + current = descending; + descending = null; + } else if (ascending != null) { + current = ascending; + ascending = null; + } + + final Content ret = current.next(); + + // got an item to return. + // sort out the next state.... + if ((ret instanceof Element) && ((Element)ret).getContentSize() > 0) { + // there is another descendant, and it has values. + // our next will be down.... + descending = ((Element)ret).getContent().iterator(); + if (ssize >= stack.length) { + stack = ArrayCopy.copyOf(stack, ssize + 16); + } + stack[ssize++] = current; + return ret; + } + + if (current.hasNext()) { + // our next will be along.... + return ret; + } + + // our next will be up. + while (ssize > 0) { + + // if the stack was generic, this would not be needed, but, + // the java.uti.* stack codes are too slow. + @SuppressWarnings("unchecked") + final Iterator subit = (Iterator)stack[--ssize]; + ascending = subit; + stack[ssize] = null; + if (ascending.hasNext()) { + return ret; + } + } + + ascending = null; + hasnext = false; + return ret; + } + + /** + * Detaches the last {@link org.jdom.Content} returned by the last call to + * next from it's parent. Note: this does not affect + * iteration and all children, siblings, and any node following the + * removed node (in document order) will be visited. + */ + @Override + public void remove() { + current.remove(); + // if our next move was to go down, we can't. + // we can go along, or up. + descending = null; + if (current.hasNext() || ascending != null) { + // we have a next element, or our next move was up anyway. + return; + } + // our next move was going to be down, or accross, but those are not + // possible any more, need to check up. + // our next will be up. + while (ssize > 0) { + @SuppressWarnings("unchecked") + final Iterator subit = (Iterator)stack[--ssize]; + stack[ssize] = null; + ascending = subit; + if (ascending.hasNext()) { + return; + } + } + ascending = null; + hasnext = false; + } + +} diff --git a/core/src/java/org/jdom/DocType.java b/core/src/java/org/jdom/DocType.java new file mode 100644 index 0000000..a7274d6 --- /dev/null +++ b/core/src/java/org/jdom/DocType.java @@ -0,0 +1,320 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.jdom.output.XMLOutputter2; + +/** + * An XML DOCTYPE declaration. Method allow the user to get and set the + * root element name, public id, system id and internal subset. + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public class DocType extends Content { + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + /** + * The element being constrained + * @serialField The root element name + */ + protected String elementName; + + /** + * The public ID of the DOCTYPE + * @serialField The PublicID, may be null + */ + protected String publicID; + + /** + * The system ID of the DOCTYPE + * @serialField The SystemID, may be null + */ + protected String systemID; + + /** + * The internal subset of the DOCTYPE + * @serialField The InternalSubset, may be null + */ + protected String internalSubset; + + /** + * Default, no-args constructor for implementations to use if needed. + */ + protected DocType() { + super(CType.DocType); + } + + /* + * XXX: + * We need to take care of entities and notations here. + */ + + /** + * This will create the DocType with + * the specified element name and a reference to an + * external DTD. + * + * @param elementName String name of + * element being constrained. + * @param publicID String public ID of + * referenced DTD + * @param systemID String system ID of + * referenced DTD + * @throws IllegalDataException if the given system ID is not a legal + * system literal or the public ID is not a legal public ID. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public DocType(String elementName, String publicID, String systemID) { + super(CType.DocType); + setElementName(elementName); + setPublicID(publicID); + setSystemID(systemID); + } + + /** + * This will create the DocType with + * the specified element name and reference to an + * external DTD. + * + * @param elementName String name of + * element being constrained. + * @param systemID String system ID of + * referenced DTD + * @throws IllegalDataException if the given system ID is not a legal + * system literal. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public DocType(String elementName, String systemID) { + this(elementName, null, systemID); + } + + /** + * This will create the DocType with + * the specified element name + * + * @param elementName String name of + * element being constrained. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public DocType(String elementName) { + this(elementName, null, null); + } + + /** + * This will retrieve the element name being constrained. + * + * @return String - element name for DOCTYPE + */ + public String getElementName() { + return elementName; + } + + /** + * This will set the root element name declared by this + * DOCTYPE declaration. + * + * @return this DocType instance + * @param elementName String name of + * root element being constrained. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public DocType setElementName(String elementName) { + // This can contain a colon so we use checkXMLName() + // instead of checkElementName() + String reason = Verifier.checkXMLName(elementName); + if (reason != null) { + throw new IllegalNameException(elementName, "DocType", reason); + } + this.elementName = elementName; + return this; + } + + /** + * This will retrieve the public ID of an externally + * referenced DTD, or an empty String if + * none is referenced. + * + * @return String - public ID of referenced DTD. + */ + public String getPublicID() { + return publicID; + } + + /** + * This will set the public ID of an externally + * referenced DTD. + * + * @param publicID id to set + * @return DocType DocType this DocType object + * @throws IllegalDataException if the given public ID is not a legal + * public ID. + */ + public DocType setPublicID(String publicID) { + String reason = Verifier.checkPublicID(publicID); + if (reason != null) { + throw new IllegalDataException(publicID, "DocType", reason); + } + this.publicID = publicID; + + return this; + } + + /** + * This will retrieve the system ID of an externally + * referenced DTD, or an empty String if + * none is referenced. + * + * @return String - system ID of referenced DTD. + */ + public String getSystemID() { + return systemID; + } + + /** + * This will set the system ID of an externally + * referenced DTD. + * + * @param systemID id to set + * @return systemID String system ID of + * referenced DTD. + * @throws IllegalDataException if the given system ID is not a legal + * system literal. + */ + public DocType setSystemID(String systemID) { + String reason = Verifier.checkSystemLiteral(systemID); + if (reason != null) { + throw new IllegalDataException(systemID, "DocType", reason); + } + this.systemID = systemID; + + return this; + } + + /** + * Returns the empty string since doctypes don't have an XPath + * 1.0 string value. + * @return the empty string + */ + @Override + public String getValue() { + return ""; // doctypes don't have an XPath string value + } + + /** + * This sets the data for the internal subset. + * + * @param newData data for the internal subset, as a + * String. + */ + public void setInternalSubset(String newData) { + internalSubset = newData; + } + + /** + * This returns the data for the internal subset. + * + * @return String - the internal subset + */ + public String getInternalSubset() { + return internalSubset; + } + + /** + * This returns a String representation of the + * DocType, suitable for debugging. + * + * @return String - information about the + * DocType + */ + @Override + public String toString() { + return new StringBuilder() + .append("[DocType: ") + .append(new XMLOutputter2().outputString(this)) + .append("]") + .toString(); + } + + @Override + public DocType clone() { + return (DocType)super.clone(); + } + + @Override + public DocType detach() { + return (DocType)super.detach(); + } + + @Override + protected DocType setParent(Parent parent) { + return (DocType)super.setParent(parent); + } + + @Override + public Document getParent() { + // because DocType can only be attached to a Document. + return (Document)super.getParent(); + } + +} diff --git a/core/src/java/org/jdom/Document.java b/core/src/java/org/jdom/Document.java new file mode 100644 index 0000000..8b825ac --- /dev/null +++ b/core/src/java/org/jdom/Document.java @@ -0,0 +1,935 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.*; + +import org.jdom.filter.AbstractFilter; +import org.jdom.filter.Filter; +import org.jdom.output.XMLOutputter2; +import org.jdom.util.IteratorIterable; + +/** + * An XML document. Methods allow access to the root element as well as the + * {@link DocType} and other document-level information. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Jools Enticknap + * @author Bradley S. Huffman + * @author Rolf Lear + */ +public class Document extends CloneBase implements Parent { + + /** + * This document's content including comments, PIs, a possible + * DocType, and a root element. + * Subclassers have to track content using their own + * mechanism. + */ + transient ContentList content = new ContentList(this); + + /** + * See http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/core.html#baseURIs-Considerations + */ + protected String baseURI = null; + + // Supports the setProperty/getProperty calls + private transient HashMap propertyMap = null; + + /** + * Creates a new empty document. A document must have a root element, + * so this document will not be well-formed and accessor methods will + * throw an IllegalStateException if this document is accessed before a + * root element is added. This method is most useful for build tools. + */ + public Document() {} + + /** + * This will create a new Document, + * with the supplied {@link Element} + * as the root element, the supplied + * {@link DocType} declaration, and the specified + * base URI. + * + * @param rootElement Element for document root. + * @param docType DocType declaration. + * @param baseURI the URI from which this doucment was loaded. + * @throws IllegalAddException if the given docType object + * is already attached to a document or the given + * rootElement already has a parent + */ + public Document(Element rootElement, DocType docType, String baseURI) { + if (rootElement != null) { + setRootElement(rootElement); + } + if (docType != null) { + setDocType(docType); + } + if (baseURI != null) { + setBaseURI(baseURI); + } + } + + /** + * This will create a new Document, + * with the supplied {@link Element} + * as the root element and the supplied + * {@link DocType} declaration. + * + * @param rootElement Element for document root. + * @param docType DocType declaration. + * @throws IllegalAddException if the given DocType object + * is already attached to a document or the given + * rootElement already has a parent + */ + public Document(Element rootElement, DocType docType) { + this(rootElement, docType, null); + } + + /** + * This will create a new Document, + * with the supplied {@link Element} + * as the root element, and no {@link DocType} + * declaration. + * + * @param rootElement Element for document root + * @throws IllegalAddException if the given rootElement already has + * a parent. + */ + public Document(Element rootElement) { + this(rootElement, null, null); + } + + /** + * This will create a new Document, + * with the supplied list of content, and a + * {@link DocType} declaration only if the content + * contains a DocType instance. A null list is treated the + * same as the no-arg constructor. + * + * @param content List of starter content + * @throws IllegalAddException if the List contains more than + * one Element or objects of illegal types. + */ + public Document(List content) { + setContent(content); + } + + @Override + public int getContentSize() { + return content.size(); + } + + @Override + public int indexOf(Content child) { + return content.indexOf(child); + } + + // /** + // * Starting at the given index (inclusive), return the index of + // * the first child matching the supplied filter, or -1 + // * if none is found. + // * + // * @return index of child, or -1 if none found. + // */ + // private int indexOf(int start, Filter filter) { + // int size = getContentSize(); + // for (int i = start; i < size; i++) { + // if (filter.matches(getContent(i))) { + // return i; + // } + // } + // return -1; + // } + + /** + * This will return true if this document has a + * root element, false otherwise. + * + * @return true if this document has a root element, + * false otherwise. + */ + public boolean hasRootElement() { + return (content.indexOfFirstElement() < 0) ? false : true; + } + + /** + * This will return the root Element + * for this Document + * + * @return Element - the document's root element + * @throws IllegalStateException if the root element hasn't been set + */ + public Element getRootElement() { + int index = content.indexOfFirstElement(); + if (index < 0) { + throw new IllegalStateException("Root element not set"); + } + return (Element) content.get(index); + } + + /** + * This sets the root {@link Element} for the + * Document. If the document already has a root + * element, it is replaced. + * + * @param rootElement Element to be new root. + * @return Document - modified Document. + * @throws IllegalAddException if the given rootElement already has + * a parent. + */ + public Document setRootElement(Element rootElement) { + int index = content.indexOfFirstElement(); + if (index < 0) { + content.add(rootElement); + } + else { + content.set(index, rootElement); + } + return this; + } + + /** + * Detach the root {@link Element} from this document. + * + * @return removed root Element + */ + public Element detachRootElement() { + int index = content.indexOfFirstElement(); + if (index < 0) + return null; + return (Element) removeContent(index); + } + + /** + * This will return the {@link DocType} + * declaration for this Document, or + * null if none exists. + * + * @return DocType - the DOCTYPE declaration. + */ + public DocType getDocType() { + int index = content.indexOfDocType(); + if (index < 0) { + return null; + } + return (DocType) content.get(index); + } + + /** + * This will set the {@link DocType} + * declaration for this Document. Note + * that a DocType can only be attached to one Document. + * Attempting to set the DocType to a DocType object + * that already belongs to a Document will result in an + * IllegalAddException being thrown. + * + * @param docType DocType declaration. + * @return object on which the method was invoked + * @throws IllegalAddException if the given docType is + * already attached to a Document. + */ + public Document setDocType(DocType docType) { + if (docType == null) { + // Remove any existing doctype + int docTypeIndex = content.indexOfDocType(); + if (docTypeIndex >= 0) content.remove(docTypeIndex); + return this; + } + + if (docType.getParent() != null) { + throw new IllegalAddException(docType, + "The DocType already is attached to a document"); + } + + // Add DocType to head if new, replace old otherwise + int docTypeIndex = content.indexOfDocType(); + if (docTypeIndex < 0) { + content.add(0, docType); + } + else { + content.set(docTypeIndex, docType); + } + + return this; + } + + /** + * Appends the child to the end of the content list. + * + * @param child child to append to end of content list + * @return the document on which the method was called + * @throws IllegalAddException if the given child already has a parent. + */ + @Override + public Document addContent(Content child) { + content.add(child); + return this; + } + + /** + * Appends all children in the given collection to the end of + * the content list. In event of an exception during add the + * original content will be unchanged and the objects in the supplied + * collection will be unaltered. + * + * @param c collection to append + * @return the document on which the method was called + * @throws IllegalAddException if any item in the collection + * already has a parent or is of an illegal type. + */ + @Override + public Document addContent(Collection c) { + content.addAll(c); + return this; + } + + /** + * Inserts the child into the content list at the given index. + * + * @param index location for adding the collection + * @param child child to insert + * @return the parent on which the method was called + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalAddException if the given child already has a parent. + */ + @Override + public Document addContent(int index, Content child) { + content.add(index, child); + return this; + } + + /** + * Inserts the content in a collection into the content list + * at the given index. In event of an exception the original content + * will be unchanged and the objects in the supplied collection will be + * unaltered. + * + * @param index location for adding the collection + * @param c collection to insert + * @return the parent on which the method was called + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalAddException if any item in the collection + * already has a parent or is of an illegal type. + */ + @Override + public Document addContent(int index, Collection c) { + content.addAll(index, c); + return this; + } + + @Override + public List cloneContent() { + int size = getContentSize(); + List list = new ArrayList(size); + for (int i = 0; i < size; i++) { + Content child = getContent(i); + list.add(child.clone()); + } + return list; + } + + @Override + public Content getContent(int index) { + return content.get(index); + } + + // public Content getChild(Filter filter) { + // int i = indexOf(0, filter); + // return (i < 0) ? null : getContent(i); + // } + + /** + * This will return all content for the Document. + * The returned list is "live" in document order and changes to it + * affect the document's actual content. + * + *

+ * Sequential traversal through the List is best done with a Iterator + * since the underlying implement of List.size() may require walking the + * entire list. + *

+ * + * @return List - all Document content + * @throws IllegalStateException if the root element hasn't been set + */ + @Override + public List getContent() { + if (!hasRootElement()) + throw new IllegalStateException("Root element not set"); + return content; + } + + /** + * Return a filtered view of this Document's content. + * + *

+ * Sequential traversal through the List is best done with a Iterator + * since the underlying implement of List.size() may require walking the + * entire list. + *

+ * + * @param filter Filter to apply + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return List - filtered Document content + * @throws IllegalStateException if the root element hasn't been set + */ + @Override + public List getContent(Filter filter) { + if (!hasRootElement()) + throw new IllegalStateException("Root element not set"); + return content.getView(filter); + } + + /** + * Removes all child content from this parent. + * + * @return list of the old children detached from this parent + */ + @Override + public List removeContent() { + List old = new ArrayList(content); + content.clear(); + return old; + } + + /** + * Remove all child content from this parent matching the supplied filter. + * + * @param filter filter to select which content to remove + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return list of the old children detached from this parent + */ + @Override + public List removeContent(Filter filter) { + List old = new ArrayList(); + Iterator itr = content.getView(filter).iterator(); + while (itr.hasNext()) { + F child = itr.next(); + old.add(child); + itr.remove(); + } + return old; + } + + /** + * This sets the content of the Document. The supplied + * List should contain only objects of type Element, + * Comment, and ProcessingInstruction. + * + *

+ * When all objects in the supplied List are legal and before the new + * content is added, all objects in the old content will have their + * parentage set to null (no parent) and the old content list will be + * cleared. This has the effect that any active list (previously obtained + * with a call to {@link #getContent}) will also + * change to reflect the new content. In addition, all objects in the + * supplied List will have their parentage set to this document, but the + * List itself will not be "live" and further removals and additions will + * have no effect on this document content. If the user wants to continue + * working with a "live" list, then a call to setContent should be + * followed by a call to {@link #getContent} to + * obtain a "live" version of the content. + *

+ * + *

+ * Passing a null or empty List clears the existing content. + *

+ * + *

+ * In event of an exception the original content will be unchanged and + * the objects in the supplied content will be unaltered. + *

+ * + * @param newContent List of content to set + * @return this document modified + * @throws IllegalAddException if the List contains objects of + * illegal types or with existing parentage. + */ + public Document setContent(Collection newContent) { + content.clearAndSet(newContent); + return this; + } + + /** + * + *

+ * Sets the effective URI from which this document was loaded, + * and against which relative URLs in this document will be resolved. + *

+ * + * @param uri the base URI of this document + */ + public final void setBaseURI(String uri) { + this.baseURI = uri; // XXX We don't check the URI + } + + /** + *

+ * Returns the URI from which this document was loaded, + * or null if this is not known. + *

+ * + * @return the base URI of this document + */ + public final String getBaseURI() { + return baseURI; + } + + /** + * Replace the current child the given index with the supplied child. + *

+ * In event of an exception the original content will be unchanged and + * the supplied child will be unaltered. + *

+ * + * @param index - index of child to replace. + * @param child - child to add. + * @return this document instance + * @throws IllegalAddException if the supplied child is already attached + * or not legal content for this parent. + * @throws IndexOutOfBoundsException if index is negative or greater + * than the current number of children. + */ + public Document setContent(int index, Content child) { + content.set(index, child); + return this; + } + + /** + * Replace the child at the given index whith the supplied + * collection. + *

+ * In event of an exception the original content will be unchanged and + * the content in the supplied collection will be unaltered. + *

+ * + * @param index - index of child to replace. + * @param collection - collection of content to add. + * @return object on which the method was invoked + * @throws IllegalAddException if the collection contains objects of + * illegal types. + * @throws IndexOutOfBoundsException if index is negative or greater + * than the current number of children. + */ + public Document setContent(int index, Collection collection) { + content.remove(index); + content.addAll(index, collection); + return this; + } + + @Override + public boolean removeContent(Content child) { + return content.remove(child); + } + + @Override + public Content removeContent(int index) { + return content.remove(index); + } + + /** + * Set this document's content to be the supplied child. + *

+ * If the supplied child is legal content for a Document and before + * it is added, all content in the current content list will + * be cleared and all current children will have their parentage set to + * null. + *

+ * This has the effect that any active list (previously obtained with + * a call to one of the {@link #getContent} methods will also change + * to reflect the new content. In addition, all content in the supplied + * collection will have their parentage set to this Document. If the user + * wants to continue working with a "live" list of this Document's + * child, then a call to setContent should be followed by a call to one + * of the {@link #getContent} methods to obtain a "live" + * version of the children. + *

+ * Passing a null child clears the existing content. + *

+ * In event of an exception the original content will be unchanged and + * the supplied child will be unaltered. + * + * @param child new content to replace existing content + * @return the parent on which the method was called + * @throws IllegalAddException if the supplied child is already attached + * or not legal content for this parent + */ + public Document setContent(Content child) { + content.clear(); + content.add(child); + return this; + } + + /** + * This returns a String representation of the + * Document, suitable for debugging. If the XML + * representation of the Document is desired, + * {@link XMLOutputter2#outputString(Document)} + * should be used. + * + * @return String - information about the + * Document + */ + @Override + public String toString() { + StringBuilder stringForm = new StringBuilder() + .append("[Document: "); + + DocType docType = getDocType(); + if (docType != null) { + stringForm.append(docType.toString()) + .append(", "); + } else { + stringForm.append(" No DOCTYPE declaration, "); + } + + Element rootElement = hasRootElement() ? getRootElement() : null ; + if (rootElement != null) { + stringForm.append("Root is ") + .append(rootElement.toString()); + } else { + stringForm.append(" No root element"); // shouldn't happen + } + + stringForm.append("]"); + + return stringForm.toString(); + } + + /** + * This tests for equality of this Document to the supplied + * Object. + * + * @param ob Object to compare to + * @return boolean whether the Document is + * equal to the supplied Object + */ + @Override + public final boolean equals(Object ob) { + return (ob == this); + } + + /** + * This returns the hash code for this Document. + * + * @return int hash code + */ + @Override + public final int hashCode() { + return super.hashCode(); + } + + /** + * This will return a deep clone of this Document. + * + * @return Object clone of this Document + */ + @Override + public Document clone() { + final Document doc = (Document) super.clone(); + + // The clone has a reference to this object's content list, so + // owerwrite with a empty list + doc.content = new ContentList(doc); + + // Add the cloned content to clone + + for (int i = 0; i < content.size(); i++) { + Object obj = content.get(i); + if (obj instanceof Element) { + Element element = ((Element)obj).clone(); + doc.content.add(element); + } + else if (obj instanceof Comment) { + Comment comment = ((Comment)obj).clone(); + doc.content.add(comment); + } + else if (obj instanceof ProcessingInstruction) { + ProcessingInstruction pi = ((ProcessingInstruction)obj).clone(); + doc.content.add(pi); + } + else if (obj instanceof DocType) { + DocType dt = ((DocType)obj).clone(); + doc.content.add(dt); + } + } + + return doc; + } + + /** + * Returns an iterator that walks over all descendants in document order. + * + * @return an iterator to walk descendants + */ + @Override + public IteratorIterable getDescendants() { + return new DescendantIterator(this); + } + + /** + * Returns an iterator that walks over all descendants in document order + * applying the Filter to return only elements that match the filter rule. + * With filters you can match only Elements, only Comments, Elements or + * Comments, only Elements with a given name and/or prefix, and so on. + * + * @param filter filter to select which descendants to see + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return an iterator to walk descendants within a filter + */ + @Override + public IteratorIterable getDescendants(final Filter filter) { + return new FilterIterator(new DescendantIterator(this), AbstractFilter.toFilter2(filter)); + } + + /** + * Always returns null, Document cannot have a parent. + * @return null + */ + @Override + public Parent getParent() { + return null; // documents never have parents + } + + + + /** + * Always returns this Document Instance + * @return 'this' because this Document is it's own Document + */ + @Override + public Document getDocument() { + return this; + } + + /** + * Assigns an arbitrary object to be associated with this document under + * the given "id" string. Null values are permitted. 'id' Strings beginning + * with "http://www.jdom.org/ are reserved for JDOM use. Properties set with + * this method will not be serialized with the rest of this Document, should + * serialization need to be done. + * + * @param id the id of the stored Object + * @param value the Object to store + */ + public void setProperty(String id, Object value) { + if (propertyMap == null) { + propertyMap = new HashMap(); + } + propertyMap.put(id, value); + } + + /** + * Returns the object associated with this document under the given "id" + * string, or null if there is no binding or if the binding explicitly + * stored a null value. + * + * @param id the id of the stored Object to return + * @return the Object associated with the given id + */ + public Object getProperty(String id) { + if (propertyMap == null) { + return null; + } + return propertyMap.get(id); + } + + @Override + public void canContainContent(Content child, int index, boolean replace) { + if (child instanceof Element) { + int cre = content.indexOfFirstElement(); + if (replace && cre == index) { + return; + } + if (cre >= 0) { + throw new IllegalAddException( + "Cannot add a second root element, only one is allowed"); + } + if (content.indexOfDocType() >= index) { + throw new IllegalAddException( + "A root element cannot be added before the DocType"); + } + } + if (child instanceof DocType) { + int cdt = content.indexOfDocType(); + if (replace && cdt == index) { + // It's OK to replace an existing DocType + return; + } + if (cdt >= 0) { + throw new IllegalAddException( + "Cannot add a second doctype, only one is allowed"); + } + int firstElt = content.indexOfFirstElement(); + if (firstElt != -1 && firstElt < index) { + throw new IllegalAddException( + "A DocType cannot be added after the root element"); + } + } + + if (child instanceof CDATA) { + throw new IllegalAddException("A CDATA is not allowed at the document root"); + } + + if (child instanceof Text) { + if(Verifier.isAllXMLWhitespace(((Text) child).getText())) { + // only whitespace, not a problem. + return; + } + throw new IllegalAddException("A Text is not allowed at the document root"); + } + + if (child instanceof EntityRef) { + throw new IllegalAddException("An EntityRef is not allowed at the document root"); + } + + } + + /** + * Get the Namespaces that are in-scope on this Document. + *

+ * Document always has exactly two Namespaces in-scope: + * {@link Namespace#NO_NAMESPACE} and {@link Namespace#XML_NAMESPACE}. + *

+ * These namespaces are always introduced by the Document, and thus they are + * both returned by {@link #getNamespacesIntroduced()}, and additionally + * {@link #getNamespacesInherited()} will always be empty. + *

+ * Description copied from + * {@link NamespaceAware#getNamespacesInScope()}: + *

+ * {@inheritDoc} + */ + @Override + public List getNamespacesInScope() { + return Collections.unmodifiableList(Arrays.asList( + new Namespace[] {Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE})); + } + + @Override + public List getNamespacesIntroduced() { + return Collections.unmodifiableList(Arrays.asList( + new Namespace[] {Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE})); + } + + @Override + public List getNamespacesInherited() { + return Collections.emptyList(); + } + + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + /** + * Serialize out the Element. + * + * @serialData + * Document Properties are not serialized! + *

+ * The Stream protocol is: + *

    + *
  1. The BaseURI using default Serialization. + *
  2. The count of child Content + *
  3. The actual Child Content. + *
+ * + * @param out where to write the Element to. + * @throws IOException if there is a writing problem. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + final int cs = content.size(); + out.writeInt(cs); + for (int i = 0; i < cs; i++) { + out.writeObject(getContent(i)); + } + } + + /** + * Read an Element off the ObjectInputStream. + * + * @see #writeObject(ObjectOutputStream) + * @param in where to read the Element from. + * @throws IOException if there is a reading problem. + * @throws ClassNotFoundException when a class cannot be found + */ + private void readObject(final ObjectInputStream in) + throws IOException, ClassNotFoundException { + + in.defaultReadObject(); + + content = new ContentList(this); + + int cs = in.readInt(); + while (--cs >= 0) { + addContent((Content)in.readObject()); + } + + } + +} diff --git a/core/src/java/org/jdom/Element.java b/core/src/java/org/jdom/Element.java new file mode 100644 index 0000000..c3b1f4b --- /dev/null +++ b/core/src/java/org/jdom/Element.java @@ -0,0 +1,2114 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import static org.jdom.JDOMConstants.NS_PREFIX_DEFAULT; +import static org.jdom.JDOMConstants.NS_PREFIX_XML; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.TreeMap; + +import org.jdom.ContentList.FilterList; +import org.jdom.filter.AbstractFilter; +import org.jdom.filter.ElementFilter; +import org.jdom.filter.Filter; +import org.jdom.output.XMLOutputter2; + +/** + * An XML element. Methods allow the user to get and manipulate its child + * elements and content, directly access the element's textual content, + * manipulate its attributes, and manage namespaces. + *

+ * See {@link NamespaceAware} and {@link #getNamespacesInScope()} for more + * details on what the Namespace scope is and how it is managed in JDOM and + * specifically by this Element class. + * + * @see NamespaceAware + * @see Content + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Lucas Gonze + * @author Kevin Regan + * @author Dan Schaffer + * @author Yusuf Goolamabbas + * @author Kent C. Johnson + * @author Jools Enticknap + * @author Alex Rosen + * @author Bradley S. Huffman + * @author Victor Toni + * @author Rolf Lear + * + */ +public class Element extends Content implements Parent { + + private static final int INITIAL_ARRAY_SIZE = 5; + + /** The local name of the element */ + protected String name; + + /** The namespace of the element */ + protected Namespace namespace; + + /** Additional namespace declarations to store on this element; useful + * during output */ + transient List additionalNamespaces = null; + + /** + * The attributes of the element. Subclassers have to + * track attributes using their own mechanism. + */ + transient AttributeList attributes = null; // = new AttributeList(this); + + /** + * The content of the element. Subclassers have to + * track content using their own mechanism. + */ + transient ContentList content = new ContentList(this); + + /** + * This protected constructor is provided in order to support an Element + * subclass that wants full control over variable initialization. It + * intentionally leaves all instance variables null, allowing a lightweight + * subclass implementation. The subclass is responsible for ensuring all the + * get and set methods on Element behave as documented. + *

+ * When implementing an Element subclass which doesn't require full control + * over variable initialization, be aware that simply calling super() (or + * letting the compiler add the implicit super() call) will not initialize + * the instance variables which will cause many of the methods to throw a + * NullPointerException. Therefore, the constructor for these subclasses + * should call one of the public constructors so variable initialization is + * handled automatically. + */ + protected Element() { + super(CType.Element); + } + + /** + * Creates a new element with the supplied (local) name and namespace. If + * the provided namespace is null, the element will have no namespace. + * + * @param name local name of the element + * @param namespace namespace for the element + * @throws IllegalNameException if the given name is illegal as an element + * name + */ + public Element(final String name, final Namespace namespace) { + super(CType.Element); + setName(name); + setNamespace(namespace); + } + + /** + * Create a new element with the supplied (local) name and no namespace. + * + * @param name local name of the element + * @throws IllegalNameException if the given name is illegal as an element + * name. + */ + public Element(final String name) { + this(name, (Namespace) null); + } + + /** + * Creates a new element with the supplied (local) name and a namespace + * given by a URI. The element will be put into the unprefixed (default) + * namespace. + * + * @param name name of the element + * @param uri namespace URI for the element + * @throws IllegalNameException if the given name is illegal as an element + * name or the given URI is illegal as a + * namespace URI + */ + public Element(final String name, final String uri) { + this(name, Namespace.getNamespace(NS_PREFIX_DEFAULT, uri)); + } + + /** + * Creates a new element with the supplied (local) name and a namespace + * given by the supplied prefix and URI combination. + * + * @param name local name of the element + * @param prefix namespace prefix + * @param uri namespace URI for the element + * @throws IllegalNameException if the given name is illegal as an element + * name, the given prefix is illegal as a + * namespace prefix, or the given URI is + * illegal as a namespace URI + */ + public Element(final String name, final String prefix, final String uri) { + this(name, Namespace.getNamespace(prefix, uri)); + } + + /** + * Returns the (local) name of the element (without any namespace prefix). + * + * @return local element name + */ + public String getName() { + return name; + } + + /** + * Sets the (local) name of the element. + * + * @param name the new (local) name of the element + * @return the target element + * @throws IllegalNameException if the given name is illegal as an Element + * name + */ + public Element setName(final String name) { + final String reason = Verifier.checkElementName(name); + if (reason != null) { + throw new IllegalNameException(name, "element", reason); + } + this.name = name; + return this; + } + + /** + * Returns the element's {@link Namespace}. + * + * @return the element's namespace + */ + public Namespace getNamespace() { + return namespace; + } + + /** + * Sets the element's {@link Namespace}. If the provided namespace is null, + * the element will have no namespace. + * + * @param namespace the new namespace. A null implies Namespace.NO_NAMESPACE. + * @return the target element + * @throws IllegalAddException if there is a Namespace conflict + */ + public Element setNamespace(Namespace namespace) { + if (namespace == null) { + namespace = Namespace.NO_NAMESPACE; + } + + if (additionalNamespaces != null) { + final String reason = Verifier.checkNamespaceCollision(namespace, + getAdditionalNamespaces()); + if (reason != null) { + throw new IllegalAddException(this, namespace, reason); + } + } + if (hasAttributes()) { + for (Attribute a : getAttributes()) { + final String reason = + Verifier.checkNamespaceCollision(namespace, a); + if (reason != null) { + throw new IllegalAddException(this, namespace, reason); + } + } + } + + this.namespace = namespace; + return this; + } + + /** + * Returns the namespace prefix of the element or an empty string if none + * exists. + * + * @return the namespace prefix + */ + public String getNamespacePrefix() { + return namespace.getPrefix(); + } + + /** + * Returns the namespace URI mapped to this element's prefix (or the + * in-scope default namespace URI if no prefix). If no mapping is found, an + * empty string is returned. + * + * @return the namespace URI for this element + */ + public String getNamespaceURI() { + return namespace.getURI(); + } + + /** + * Returns the {@link Namespace} corresponding to the given prefix in scope + * for this element. This involves searching up the tree, so the results + * depend on the current location of the element. Returns null if there is + * no namespace in scope with the given prefix at this point in the + * document. + * + * @param prefix namespace prefix to look up + * @return the Namespace for this prefix at this + * location, or null if none + */ + public Namespace getNamespace(final String prefix) { + if (prefix == null) { + return null; + } + + if (NS_PREFIX_XML.equals(prefix)) { + // Namespace "xml" is always bound. + return Namespace.XML_NAMESPACE; + } + + // Check if the prefix is the prefix for this element + if (prefix.equals(getNamespacePrefix())) { + return getNamespace(); + } + + // Scan the additional namespaces + if (additionalNamespaces != null) { + for (int i = 0; i < additionalNamespaces.size(); i++) { + final Namespace ns = additionalNamespaces.get(i); + if (prefix.equals(ns.getPrefix())) { + return ns; + } + } + } + + if (attributes != null) { + for (final Attribute a : attributes) { + if (prefix.equals(a.getNamespacePrefix())) { + return a.getNamespace(); + } + } + } + + // If we still don't have a match, ask the parent + if (parent instanceof Element) { + return ((Element)parent).getNamespace(prefix); + } + + return null; + } + + /** + * Returns the full name of the element, in the form + * [namespacePrefix]:[localName]. If the element does not have a namespace + * prefix, then the local name is returned. + * + * @return qualified name of the element (including + * namespace prefix) + */ + public String getQualifiedName() { + // Note: Any changes here should be reflected in + // XMLOutputter.printQualifiedName() + if ("".equals(namespace.getPrefix())) { + return getName(); + } + + return new StringBuilder(namespace.getPrefix()) + .append(':') + .append(name) + .toString(); + } + + /** + * Adds a namespace declarations to this element. This should not be + * used to add the declaration for this element itself; that should be + * assigned in the construction of the element. Instead, this is for adding + * namespace declarations on the element not relating directly to itself. + * It's used during output to for stylistic reasons move namespace + * declarations higher in the tree than they would have to be. + * + * @param additionalNamespace namespace to add + * @return true if the namespace is added (false if it was previously added) + * @throws IllegalAddException if the namespace prefix collides with another + * namespace prefix on the element + */ + public void addNamespaceDeclaration(final Namespace additionalNamespace) { + + if (additionalNamespaces == null) { + additionalNamespaces = new ArrayList(INITIAL_ARRAY_SIZE); + } + + for (Namespace ns : additionalNamespaces) { + if (ns == additionalNamespace) { + return; + } + } + + // Verify the new namespace prefix doesn't collide with another + // declared namespace, an attribute prefix, or this element's prefix + final String reason = Verifier.checkNamespaceCollision(additionalNamespace, this); + if (reason != null) { + throw new IllegalAddException(this, additionalNamespace, reason); + } + + additionalNamespaces.add(additionalNamespace); + } + + /** + * Removes an additional namespace declarations from this element. This + * should not be used to remove the declaration for this element + * itself; that should be handled in the construction of the element. + * Instead, this is for removing namespace declarations on the element not + * relating directly to itself. If the declaration is not present, this + * method does nothing. + * + * @param additionalNamespace namespace to remove. A null Namespace does nothing. + */ + public void removeNamespaceDeclaration(final Namespace additionalNamespace) { + if (additionalNamespaces == null) { + return; + } + additionalNamespaces.remove(additionalNamespace); + } + + /** + * Returns a list of the additional namespace declarations on this element. + * This includes only additional namespace, not the namespace of the element + * itself, which can be obtained through {@link #getNamespace()}. If there + * are no additional declarations, this returns an empty list. Note, the + * returned list is unmodifiable. + * + * @return a List of the additional namespace + * declarations + */ + public List getAdditionalNamespaces() { + // Not having the returned list be live allows us to avoid creating a + // new list object when XMLOutputter calls this method on an element + // with an empty list. + if (additionalNamespaces == null) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(additionalNamespaces); + } + + /** + * Returns the XPath 1.0 string value of this element, which is the + * complete, ordered content of all text node descendants of this element + * (i.e. the text that's left after all references are resolved + * and all other markup is stripped out.) + * + * @return a concatentation of all text node descendants + */ + @Override + public String getValue() { + final StringBuilder buffer = new StringBuilder(); + + for (Content child : getContent()) { + if (child instanceof Element || child instanceof Text) { + buffer.append(child.getValue()); + } + } + return buffer.toString(); + } + + /** + * Returns whether this element is a root element. This can be used in + * tandem with {@link #getParent} to determine if an element has any + * "attachments" to a parent element or document. + *

+ * An element is a root element when it has a parent and that parent is a + * Document. In particular, this means that detatched Elements are not + * root elements. + * + * @return whether this is a root element + */ + public boolean isRootElement() { + return parent instanceof Document; + } + + @Override + public int getContentSize() { + return content.size(); + } + + @Override + public int indexOf(final Content child) { + return content.indexOf(child); + } + + // private int indexOf(int start, Filter filter) { + // int size = getContentSize(); + // for (int i = start; i < size; i++) { + // if (filter.matches(getContent(i))) { + // return i; + // } + // } + // return -1; + // } + + + /** + * Returns the textual content directly held under this element as a string. + * This includes all text within this single element, including whitespace + * and CDATA sections if they exist. It's essentially the concatenation of + * all {@link Text} and {@link CDATA} nodes returned by {@link #getContent}. + * The call does not recurse into child elements. If no textual value exists + * for the element, an empty string is returned. + * + * @return text content for this element, or empty + * string if none + */ + public String getText() { + if (content.size() == 0) { + return ""; + } + + // If we hold only a Text or CDATA, return it directly + if (content.size() == 1) { + final Object obj = content.get(0); + if (obj instanceof Text) { + return ((Text) obj).getText(); + } + return ""; + } + + // Else build String up + final StringBuilder textContent = new StringBuilder(); + boolean hasText = false; + + for (int i = 0; i < content.size(); i++) { + final Object obj = content.get(i); + if (obj instanceof Text) { + textContent.append(((Text) obj).getText()); + hasText = true; + } + } + + if (!hasText) { + return ""; + } + return textContent.toString(); + } + + /** + * Returns the textual content of this element with all surrounding + * whitespace removed. If no textual value exists for the element, or if + * only whitespace exists, the empty string is returned. + * + * @return trimmed text content for this element, or + * empty string if none + */ + public String getTextTrim() { + return getText().trim(); + } + + /** + * Returns the textual content of this element with all surrounding + * whitespace removed and internal whitespace normalized to a single space. + * If no textual value exists for the element, or if only whitespace exists, + * the empty string is returned. + * + * @return normalized text content for this element, or + * empty string if none + */ + public String getTextNormalize() { + return Text.normalizeString(getText()); + } + + /** + * Returns the textual content of the named child element, or null if + * there's no such child. This method is a convenience because calling + * getChild().getText() can throw a NullPointerException. + * + * @param cname the name of the child + * @return text content for the named child, or null if + * no such child + */ + public String getChildText(final String cname) { + final Element child = getChild(cname); + if (child == null) { + return null; + } + return child.getText(); + } + + /** + * Returns the trimmed textual content of the named child element, or null + * if there's no such child. See {@link #getTextTrim()} for + * details of text trimming. + * + * @param cname the name of the child + * @return trimmed text content for the named child, or + * null if no such child + */ + public String getChildTextTrim(final String cname) { + final Element child = getChild(cname); + if (child == null) { + return null; + } + return child.getTextTrim(); + } + + /** + * Returns the normalized textual content of the named child element, or + * null if there's no such child. See {@link + * #getTextNormalize()} for details of text normalizing. + * + * @param cname the name of the child + * @return normalized text content for the named child, + * or null if no such child + */ + public String getChildTextNormalize(final String cname) { + final Element child = getChild(cname); + if (child == null) { + return null; + } + return child.getTextNormalize(); + } + + /** + * Returns the textual content of the named child element, or null if + * there's no such child. + * + * @param cname + * the name of the child + * @param ns + * the namespace of the child. A null implies Namespace.NO_NAMESPACE. + * @return text content for the named child, or null if no such child + */ + public String getChildText(final String cname, final Namespace ns) { + final Element child = getChild(cname, ns); + if (child == null) { + return null; + } + return child.getText(); + } + + /** + * Returns the trimmed textual content of the named child element, or null + * if there's no such child. + * + * @param cname + * the name of the child + * @param ns + * the namespace of the child. A null implies Namespace.NO_NAMESPACE. + * @return trimmed text content for the named child, or null if no such + * child + */ + public String getChildTextTrim(final String cname, final Namespace ns) { + final Element child = getChild(cname, ns); + if (child == null) { + return null; + } + return child.getTextTrim(); + } + + /** + * Returns the normalized textual content of the named child element, or + * null if there's no such child. + * + * @param cname + * the name of the child + * @param ns + * the namespace of the child. A null implies Namespace.NO_NAMESPACE. + * @return normalized text content for the named child, or null if no such + * child + */ + public String getChildTextNormalize(final String cname, final Namespace ns) { + final Element child = getChild(cname, ns); + if (child == null) { + return null; + } + return child.getTextNormalize(); + } + + /** + * Sets the content of the element to be the text given. All existing text + * content and non-text context is removed. If this element should have both + * textual content and nested elements, use {@link #setContent} + * instead. Setting a null text value is equivalent to setting an empty + * string value. + * + * @param text new text content for the element + * @return the target element + * @throws IllegalDataException if the assigned text contains an illegal + * character such as a vertical tab (as + * determined by {@link + * org.jdom.Verifier#checkCharacterData}) + */ + public Element setText(final String text) { + content.clear(); + + if (text != null) { + addContent(new Text(text)); + } + + return this; + } + + /** + * Adjacent Text content is merged into the first Text in document order, + * and the redundant Text items are removed (including any empty Text). + * + * @param recursively + * true if you want the text of child elements coalesced too. False + * if you only want to coalesce this Element's Text. + * @return true if any content was changed by this operation. + */ + public boolean coalesceText(boolean recursively) { + final Iterator it = recursively ? getDescendants() + : content.iterator(); + Text tfirst = null; + boolean changed = false; + while (it.hasNext()) { + final Content c = it.next(); + if (c.getCType() == CType.Text) { + // Text, and no CDATA! + final Text text = (Text)c; + if ("".equals(text.getValue())) { + it.remove(); + changed = true; + } else if (tfirst == null || + tfirst.getParent() != text.getParent()) { + // previous item in the iterator was not text, or + // we are the next Text item after coming up the tree. + tfirst = text; + } else { + // add our text to the first in the sequence + tfirst.append(text.getValue()); + // remove us from the sequence + it.remove(); + changed = true; + } + } else { + // the end of the sequence + tfirst = null; + } + } + return changed; + } + + /** + * This returns the full content of the element as a List which + * may contain objects of type Text, Element, + * Comment, ProcessingInstruction, + * CDATA, and EntityRef. + * The List returned is "live" in document order and modifications + * to it affect the element's actual contents. Whitespace content is + * returned in its entirety. + * + *

+ * Sequential traversal through the List is best done with an Iterator + * since the underlying implement of List.size() may require walking the + * entire list. + *

+ * + * @return a List containing the mixed content of the + * element: may contain Text, + * {@link Element}, {@link Comment}, + * {@link ProcessingInstruction}, + * {@link CDATA}, and + * {@link EntityRef} objects. + */ + @Override + public List getContent() { + return content; + } + + /** + * Return a filter view of this Element's content. + * + *

+ * Sequential traversal through the List is best done with a Iterator + * since the underlying implement of List.size() may require walking the + * entire list. + *

+ * + * @param filter Filter to apply + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return List - filtered Element content + */ + @Override + public List getContent(final Filter filter) { + return content.getView(filter); + } + + /** + * Removes all child content from this parent. + * + * @return list of the old children detached from this parent + */ + @Override + public List removeContent() { + final List old = new ArrayList(content); + content.clear(); + return old; + } + + /** + * Remove all child content from this parent matching the supplied filter. + * + * @param filter filter to select which content to remove + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return list of the old children detached from this parent + */ + @Override + public List removeContent(final Filter filter) { + final List old = new ArrayList(); + final Iterator iter = content.getView(filter).iterator(); + while (iter.hasNext()) { + final F child = iter.next(); + old.add(child); + iter.remove(); + } + return old; + } + + /** + * This sets the content of the element. The supplied List should + * contain only objects of type Element, Text, + * CDATA, Comment, + * ProcessingInstruction, and EntityRef. + * + *

+ * When all objects in the supplied List are legal and before the new + * content is added, all objects in the old content will have their + * parentage set to null (no parent) and the old content list will be + * cleared. This has the effect that any active list (previously obtained + * with a call to {@link #getContent} or {@link #getChildren}) will also + * change to reflect the new content. In addition, all objects in the + * supplied List will have their parentage set to this element, but the + * List itself will not be "live" and further removals and additions will + * have no effect on this elements content. If the user wants to continue + * working with a "live" list, then a call to setContent should be + * followed by a call to {@link #getContent} or {@link #getChildren} to + * obtain a "live" version of the content. + *

+ * + *

+ * Passing a null or empty List clears the existing content. + *

+ * + *

+ * In event of an exception the original content will be unchanged and + * the objects in the supplied content will be unaltered. + *

+ * + * @param newContent Collection of content to set + * @return this element modified + * @throws IllegalAddException if the List contains objects of + * illegal types or with existing parentage. + */ + public Element setContent(final Collection newContent) { + content.clearAndSet(newContent); + return this; + } + + /** + * Replace the current child the given index with the supplied child. + *

+ * In event of an exception the original content will be unchanged and + * the supplied child will be unaltered. + *

+ * + * @param index - index of child to replace. + * @param child - child to add. + * @return element on which this method was invoked + * @throws IllegalAddException if the supplied child is already attached + * or not legal content for this parent. + * @throws IndexOutOfBoundsException if index is negative or greater + * than the current number of children. + */ + public Element setContent(final int index, final Content child) { + content.set(index, child); + return this; + } + + /** + * Replace the child at the given index whith the supplied + * collection. + *

+ * In event of an exception the original content will be unchanged and + * the content in the supplied collection will be unaltered. + *

+ * + * @param index - index of child to replace. + * @param newContent - Collection of content to replace child. + * @return object on which this method was invoked + * @throws IllegalAddException if the collection contains objects of + * illegal types. + * @throws IndexOutOfBoundsException if index is negative or greater + * than the current number of children. + */ + public Parent setContent(final int index, final Collection newContent) { + content.remove(index); + content.addAll(index, newContent); + return this; + } + + /** + * This adds text content to this element. It does not replace the + * existing content as does setText(). + * + * @param str String to add + * @return this element modified + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + */ + public Element addContent(final String str) { + return addContent(new Text(str)); + } + + /** + * Appends the child to the end of the element's content list. + * + * @param child child to append to end of content list + * @return the element on which the method was called + * @throws IllegalAddException if the given child already has a parent. */ + @Override + public Element addContent(final Content child) { + content.add(child); + return this; + } + + /** + * The same as {@link #addContent(Content)}, added to keep binary compatibility. + */ + public Element addContent(Element child) { + content.add(child); + return this; + } + + + /** + * Appends all children in the given collection to the end of + * the content list. In event of an exception during add the + * original content will be unchanged and the objects in the supplied + * collection will be unaltered. + * + * @param newContent Collection of content to append + * @return the element on which the method was called + * @throws IllegalAddException if any item in the collection + * already has a parent or is of an inappropriate type. + */ + @Override + public Element addContent(final Collection newContent) { + content.addAll(newContent); + return this; + } + + /** + * Inserts the child into the content list at the given index. + * + * @param index location for adding the collection + * @param child child to insert + * @return the parent on which the method was called + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalAddException if the given child already has a parent. + */ + @Override + public Element addContent(final int index, final Content child) { + content.add(index, child); + return this; + } + + /** + * Inserts the content in a collection into the content list + * at the given index. In event of an exception the original content + * will be unchanged and the objects in the supplied collection will be + * unaltered. + * + * @param index location for adding the collection + * @param newContent Collection of content to insert + * @return the parent on which the method was called + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalAddException if any item in the collection + * already has a parent or is of an inappropriate type. + */ + @Override + public Element addContent(final int index, final Collection newContent) { + content.addAll(index, newContent); + return this; + } + + @Override + public List cloneContent() { + final int size = getContentSize(); + final List list = new ArrayList(size); + for (int i = 0; i < size; i++) { + final Content child = getContent(i); + list.add(child.clone()); + } + return list; + } + + @Override + public Content getContent(final int index) { + return content.get(index); + } + + // public Content getChild(Filter filter) { + // int i = indexOf(0, filter); + // return (i < 0) ? null : getContent(i); + // } + + @Override + public boolean removeContent(final Content child) { + return content.remove(child); + } + + @Override + public Content removeContent(final int index) { + return content.remove(index); + } + + /** + * Set this element's content to be the supplied child. + *

+ * If the supplied child is legal content for this parent and before + * it is added, all content in the current content list will + * be cleared and all current children will have their parentage set to + * null. + *

+ * This has the effect that any active list (previously obtained with + * a call to one of the {@link #getContent} methods will also change + * to reflect the new content. In addition, all content in the supplied + * collection will have their parentage set to this parent. If the user + * wants to continue working with a "live" list of this parent's + * child, then a call to setContent should be followed by a call to one + * of the {@link #getContent} methods to obtain a "live" + * version of the children. + *

+ * Passing a null child clears the existing content. + *

+ * In event of an exception the original content will be unchanged and + * the supplied child will be unaltered. + * + * @param child new content to replace existing content + * @return the parent on which the method was called + * @throws IllegalAddException if the supplied child is already attached + * or not legal content for an Element + */ + public Element setContent(final Content child) { + content.clear(); + content.add(child); + return this; + } + + + /** + * Determines if this element is the ancestor of another element. + * + * @param element Element to check against + * @return true if this element is the ancestor of the + * supplied element + */ + public boolean isAncestor(final Element element) { + Parent p = element.getParent(); + while (p instanceof Element) { + if (p == this) { + return true; + } + p = p.getParent(); + } + return false; + } + + /** + * Indicate whether this Element has any attributes. + * Where possible you should call this method before calling getAttributes() + * because calling getAttributes() will create the necessary Attribute List + * memory structures, even if there are no Attributes attached to the + * Element. Calling hasAttributes() first can save memory. + * @return true if this Element has attributes. + */ + public boolean hasAttributes() { + return attributes != null && !attributes.isEmpty(); + } + + /** + * Indicate whether this Element has any additional Namespace declarations. + * Where possible you should call this method before calling + * {@link #getAdditionalNamespaces()} because calling getAttributes() will + * create an unnecessary List even if there are no Additional Namespaces + * attached to the Element. Calling this method first can save memory and + * time. + * @return true if this Element has additional Namespaces. + */ + public boolean hasAdditionalNamespaces() { + return additionalNamespaces != null && !additionalNamespaces.isEmpty(); + } + + /** + * Lazy initialiser for the Attribute list. + * @return this Element's Attribute List (creating it if necessary). + */ + AttributeList getAttributeList() { + if (attributes == null) { + attributes = new AttributeList(this); + } + return attributes; + } + + /** + *

+ * This returns the complete set of attributes for this element, as a + * List of Attribute objects in no particular + * order, or an empty list if there are none. + * The returned list is "live" and changes to it affect the + * element's actual attributes. + *

+ * + * @return attributes for the element + */ + public List getAttributes() { + return getAttributeList(); + } + + /** + *

+ * This returns the attribute for this element with the given name + * and within no namespace, or null if no such attribute exists. + *

+ * + * @param attname name of the attribute to return + * @return attribute for the element + */ + public Attribute getAttribute(final String attname) { + return getAttribute(attname, Namespace.NO_NAMESPACE); + } + + /** + *

+ * This returns the attribute for this element with the given name + * and within the given Namespace, or null if no such attribute exists. + *

+ * + * @param attname name of the attribute to return + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @return attribute for the element + */ + public Attribute getAttribute(final String attname, final Namespace ns) { + if (attributes == null) { + return null; + } + return getAttributeList().get(attname, ns); + } + + /** + *

+ * This returns the attribute value for the attribute with the given name + * and within no namespace, null if there is no such attribute, and the + * empty string if the attribute value is empty. + *

+ * + * @param attname name of the attribute whose value to be returned + * @return the named attribute's value, or null if no such attribute + */ + public String getAttributeValue(final String attname) { + if (attributes == null) { + return null; + } + return getAttributeValue(attname, Namespace.NO_NAMESPACE); + } + + /** + *

+ * This returns the attribute value for the attribute with the given name + * and within no namespace, or the passed-in default if there is no + * such attribute. + *

+ * + * @param attname name of the attribute whose value to be returned + * @param def a default value to return if the attribute does not exist + * @return the named attribute's value, or the default if no such attribute + */ + public String getAttributeValue(final String attname, final String def) { + if (attributes == null) { + return def; + } + return getAttributeValue(attname, Namespace.NO_NAMESPACE, def); + } + + /** + *

+ * This returns the attribute value for the attribute with the given name + * and within the given Namespace, null if there is no such attribute, and + * the empty string if the attribute value is empty. + *

+ * + * @param attname name of the attribute whose valud is to be returned + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @return the named attribute's value, or null if no such attribute + */ + public String getAttributeValue(final String attname, final Namespace ns) { + if (attributes == null) { + return null; + } + return getAttributeValue(attname, ns, null); + } + + /** + *

+ * This returns the attribute value for the attribute with the given name + * and within the given Namespace, or the passed-in default if there is no + * such attribute. + *

+ * + * @param attname name of the attribute whose valud is to be returned + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @param def a default value to return if the attribute does not exist + * @return the named attribute's value, or the default if no such attribute + */ + public String getAttributeValue(final String attname, final Namespace ns, final String def) { + if (attributes == null) { + return def; + } + final Attribute attribute = getAttributeList().get(attname, ns); + if (attribute == null) { + return def; + } + + return attribute.getValue(); + } + + /** + *

+ * This sets the attributes of the element. The supplied Collection should + * contain only objects of type Attribute. + *

+ * + *

+ * When all objects in the supplied List are legal and before the new + * attributes are added, all old attributes will have their + * parentage set to null (no parent) and the old attribute list will be + * cleared. This has the effect that any active attribute list (previously + * obtained with a call to {@link #getAttributes}) will also change to + * reflect the new attributes. In addition, all attributes in the supplied + * List will have their parentage set to this element, but the List itself + * will not be "live" and further removals and additions will have no + * effect on this elements attributes. If the user wants to continue + * working with a "live" attribute list, then a call to setAttributes + * should be followed by a call to {@link #getAttributes} to obtain a + * "live" version of the attributes. + *

+ * + *

+ * Passing a null or empty List clears the existing attributes. + *

+ * + *

+ * In cases where the List contains duplicate attributes, only the last + * one will be retained. This has the same effect as calling + * {@link #setAttribute(Attribute)} sequentially. + *

+ * + *

+ * In event of an exception the original attributes will be unchanged and + * the attributes in the supplied attributes will be unaltered. + *

+ * + * @param newAttributes Collection of attributes to set + * @return this element modified + * @throws IllegalAddException if the List contains objects + * that are not instances of Attribute, + * or if any of the Attribute objects have + * conflicting namespace prefixes. + */ + public Element setAttributes(final Collection newAttributes) { + getAttributeList().clearAndSet(newAttributes); + return this; + } + + /** + * The same as {@link #setAttributes(Collection)}, added to keep binary compatibility. + */ + public Element setAttributes(final List newAttributes) { + return setAttributes((Collection) newAttributes); + } + + /** + *

+ * This sets an attribute value for this element. Any existing attribute + * with the same name and namespace URI is removed. + *

+ * + * @param name name of the attribute to set + * @param value value of the attribute to set + * @return this element modified + * @throws IllegalNameException if the given name is illegal as an + * attribute name. + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}). + */ + public Element setAttribute(final String name, final String value) { + final Attribute attribute = getAttribute(name); + if (attribute == null) { + final Attribute newAttribute = new Attribute(name, value); + setAttribute(newAttribute); + } else { + attribute.setValue(value); + } + + return this; + } + + /** + *

+ * This sets an attribute value for this element. Any existing attribute + * with the same name and namespace URI is removed. + *

+ * + * @param name name of the attribute to set + * @param value value of the attribute to set + * @param ns namespace of the attribute to set. A null implies Namespace.NO_NAMESPACE. + * @return this element modified + * @throws IllegalNameException if the given name is illegal as an + * attribute name, or if the namespace is an unprefixed default + * namespace + * @throws IllegalDataException if the given attribute value is + * illegal character data (as determined by + * {@link org.jdom.Verifier#checkCharacterData}). + * @throws IllegalAddException if the attribute namespace prefix + * collides with another namespace prefix on the element. + */ + public Element setAttribute(final String name, final String value, final Namespace ns) { + final Attribute attribute = getAttribute(name, ns); + if (attribute == null) { + final Attribute newAttribute = new Attribute(name, value, ns); + setAttribute(newAttribute); + } else { + attribute.setValue(value); + } + + return this; + } + + /** + *

+ * This sets an attribute value for this element. Any existing attribute + * with the same name and namespace URI is removed. + *

+ * + * @param attribute Attribute to set + * @return this element modified + * @throws IllegalAddException if the attribute being added already has a + * parent or if the attribute namespace prefix collides with another + * namespace prefix on the element. + */ + public Element setAttribute(final Attribute attribute) { + getAttributeList().add(attribute); + return this; + } + + /** + *

+ * This removes the attribute with the given name and within no + * namespace. If no such attribute exists, this method does nothing. + *

+ * + * @param attname name of attribute to remove + * @return whether the attribute was removed + */ + public boolean removeAttribute(final String attname) { + return removeAttribute(attname, Namespace.NO_NAMESPACE); + } + + /** + *

+ * This removes the attribute with the given name and within the + * given Namespace. If no such attribute exists, this method does + * nothing. + *

+ * + * @param attname name of attribute to remove + * @param ns namespace URI of attribute to remove. A null implies Namespace.NO_NAMESPACE. + * @return whether the attribute was removed + */ + public boolean removeAttribute(final String attname, final Namespace ns) { + if (attributes == null) { + return false; + } + return getAttributeList().remove(attname, ns); + } + + /** + *

+ * This removes the supplied Attribute should it exist. + *

+ * + * @param attribute Reference to the attribute to be removed. + * @return whether the attribute was removed + */ + public boolean removeAttribute(final Attribute attribute) { + if (attributes == null) { + return false; + } + return getAttributeList().remove(attribute); + } + + /** + *

+ * This returns a String representation of the + * Element, suitable for debugging. If the XML + * representation of the Element is desired, + * {@link XMLOutputter2#outputString(Element)} + * should be used. + *

+ * + * @return String - information about the + * Element + */ + @Override + public String toString() { + final StringBuilder stringForm = new StringBuilder(64) + .append("[Element: <") + .append(getQualifiedName()); + + final String nsuri = getNamespaceURI(); + if (!"".equals(nsuri)) { + stringForm + .append(" [Namespace: ") + .append(nsuri) + .append("]"); + } + stringForm.append("/>]"); + + return stringForm.toString(); + } + + /** + *

+ * This returns a deep clone of this element. + * The new element is detached from its parent, and getParent() + * on the clone will return null. + *

+ * + * @return the clone of this element + */ + @Override + public Element clone() { + + // Ken Rune Helland is our local clone() guru + + final Element element = (Element) super.clone(); + + // name and namespace are references to immutable objects + // so super.clone() handles them ok + + // Reference to parent is copied by super.clone() + // (Object.clone()) so we have to remove it + // Actually, super is a Content, which has already detached in the + // clone(). + // element.parent = null; + + // Reference to content list and attribute lists are copyed by + // super.clone() so we set it new lists if the original had lists + element.content = new ContentList(element); + element.attributes = attributes == null ? null : new AttributeList(element); + + // Cloning attributes + if (attributes != null) { + for(int i = 0; i < attributes.size(); i++) { + final Attribute attribute = attributes.get(i); + element.attributes.add(attribute.clone()); + } + } + + // Cloning additional namespaces + if (additionalNamespaces != null) { + element.additionalNamespaces = new ArrayList(additionalNamespaces); + } + + // Cloning content + for(int i = 0; i < content.size(); i++) { + final Content c = content.get(i); + element.content.add(c.clone()); + } + + return element; + } + + + /** + * Returns an iterator that walks over all descendants in document order. + * + * @return an iterator to walk descendants + */ + @Override + public Iterator getDescendants() { + return new DescendantIterator(this); + } + + /** + * Returns an iterator that walks over all descendants in document order + * applying the Filter to return only content that match the filter rule. + * With filters you can match only Elements, only Comments, Elements or + * Comments, only Elements with a given name and/or prefix, and so on. + * + * @param filter filter to select which descendants to see + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return an iterator to walk descendants within a filter + */ + @Override + public Iterator getDescendants(final Filter filter) { + return new FilterIterator(new DescendantIterator(this), AbstractFilter.toFilter2(filter)); + } + + + + /** + * This returns a List of all the child elements + * nested directly (one level deep) within this element, as + * Element objects. If this target element has no nested + * elements, an empty List is returned. The returned list is "live" + * in document order and changes to it affect the element's actual + * contents. + * + *

+ * Sequential traversal through the List is best done with a Iterator + * since the underlying implement of List.size() may not be the most + * efficient. + *

+ * + *

+ * No recursion is performed, so elements nested two levels deep + * would have to be obtained with: + *

+	 * 
+	 *   for(Element oneLevelDeep : topElement.getChildren()) {
+	 *     List<Element> twoLevelsDeep = oneLevelDeep.getChildren();
+	 *     // Do something with these children
+	 *   }
+	 * 
+	 * 
+ *

+ * + * @return list of child Element objects for this element + */ + public List getChildren() { + return content.getView(new ElementFilter()); + } + + /** + * This returns a List of all the child elements + * nested directly (one level deep) within this element with the given + * local name and belonging to no namespace, returned as + * Element objects. If this target element has no nested + * elements with the given name outside a namespace, an empty List + * is returned. The returned list is "live" in document order + * and changes to it affect the element's actual contents. + *

+ * Please see the notes for {@link #getChildren} + * for a code example. + *

+ * + * @param cname local name for the children to match + * @return all matching child elements + */ + public List getChildren(final String cname) { + return getChildren(cname, Namespace.NO_NAMESPACE); + } + + /** + * This returns a List of all the child elements + * nested directly (one level deep) within this element with the given + * local name and belonging to the given Namespace, returned as + * Element objects. If this target element has no nested + * elements with the given name in the given Namespace, an empty List + * is returned. The returned list is "live" in document order + * and changes to it affect the element's actual contents. + *

+ * Please see the notes for {@link #getChildren} + * for a code example. + *

+ * + * @param cname local name for the children to match + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @return all matching child elements + */ + public List getChildren(final String cname, final Namespace ns) { + return content.getView(new ElementFilter(cname, ns)); + } + + /** + * This returns the first child element within this element with the + * given local name and belonging to the given namespace. + * If no elements exist for the specified name and namespace, null is + * returned. + * + * @param cname local name of child element to match + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @return the first matching child element, or null if not found + */ + public Element getChild(final String cname, final Namespace ns) { + final List elements = content.getView(new ElementFilter(cname, ns)); + final Iterator iter = elements.iterator(); + if (iter.hasNext()) { + return iter.next(); + } + return null; + } + + /** + * This returns the first child element within this element with the + * given local name and belonging to no namespace. + * If no elements exist for the specified name and namespace, null is + * returned. + * + * @param cname local name of child element to match + * @return the first matching child element, or null if not found + */ + public Element getChild(final String cname) { + return getChild(cname, Namespace.NO_NAMESPACE); + } + + /** + *

+ * This removes the first child element (one level deep) with the + * given local name and belonging to no namespace. + * Returns true if a child was removed. + *

+ * + * @param cname the name of child elements to remove + * @return whether deletion occurred + */ + public boolean removeChild(final String cname) { + return removeChild(cname, Namespace.NO_NAMESPACE); + } + + /** + *

+ * This removes the first child element (one level deep) with the + * given local name and belonging to the given namespace. + * Returns true if a child was removed. + *

+ * + * @param cname the name of child element to remove + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @return whether deletion occurred + */ + public boolean removeChild(final String cname, final Namespace ns) { + final ElementFilter filter = new ElementFilter(cname, ns); + final List old = content.getView(filter); + final Iterator iter = old.iterator(); + if (iter.hasNext()) { + iter.next(); + iter.remove(); + return true; + } + + return false; + } + + /** + *

+ * This removes all child elements (one level deep) with the + * given local name and belonging to no namespace. + * Returns true if any were removed. + *

+ * + * @param cname the name of child elements to remove + * @return whether deletion occurred + */ + public boolean removeChildren(final String cname) { + return removeChildren(cname, Namespace.NO_NAMESPACE); + } + + /** + *

+ * This removes all child elements (one level deep) with the + * given local name and belonging to the given namespace. + * Returns true if any were removed. + *

+ * + * @param cname the name of child elements to remove + * @param ns Namespace to search within. A null implies Namespace.NO_NAMESPACE. + * @return whether deletion occurred + */ + public boolean removeChildren(final String cname, final Namespace ns) { + boolean deletedSome = false; + + final ElementFilter filter = new ElementFilter(cname, ns); + final List old = content.getView(filter); + final Iterator iter = old.iterator(); + while (iter.hasNext()) { + iter.next(); + iter.remove(); + deletedSome = true; + } + + return deletedSome; + } + + /** + * Get the Namespaces that are in-scope on this Element. Element has the + * most complex rules for the namespaces-in-scope. + *

+ * The scope is built up from a number of sources following the rules of + * XML namespace inheritence as follows: + *

    + *
  • The {@link Namespace#XML_NAMESPACE} is added + *
  • The element's namespace is added (commonly + * {@link Namespace#NO_NAMESPACE}) + *
  • All the attributes are inspected and their Namespaces are included + *
  • All Namespaces declared on this Element using + * {@link #addNamespaceDeclaration(Namespace)} are included. + *
  • If the element has a parent then the parent's Namespace scope is + * inspected, and any prefixes in the parent scope that are not yet bound + * in this Element's scope are included. + *
  • If the default Namespace (the no-prefix namespace) has not been + * encountered for this Element then {@link Namespace#NO_NAMESPACE} is + * included. + *
+ * The Element's Namespace scope consist of it's inherited Namespaces and + * any modifications to that scope derived from the Element itself. If the + * element is detached then it's inherited scope consists of just + * If an element has no parent then + *

+ * Note that the Element's Namespace will always be reported first. + *

+ * Description copied from + * {@link NamespaceAware#getNamespacesInScope()}: + *

+ * {@inheritDoc} + * + * @see NamespaceAware + */ + @Override + public List getNamespacesInScope() { + // The assumption here is that all namespaces are valid, + // that there are no namespace collisions on this element + + // This method is also the 'anchor' of the three getNamespaces*() methods + // It does not make reference to this Element instance's other + // getNamespace*() methods + + TreeMap namespaces = new TreeMap(); + namespaces.put(Namespace.XML_NAMESPACE.getPrefix(), Namespace.XML_NAMESPACE); + namespaces.put(getNamespacePrefix(), getNamespace()); + if (additionalNamespaces != null) { + for (Namespace ns : getAdditionalNamespaces()) { + if (!namespaces.containsKey(ns.getPrefix())) { + namespaces.put(ns.getPrefix(), ns); + } + } + } + if (attributes != null) { + for (Attribute att : getAttributes()) { + Namespace ns = att.getNamespace(); + if (!namespaces.containsKey(ns.getPrefix())) { + namespaces.put(ns.getPrefix(), ns); + } + } + } + // Right, we now have all the namespaces that are current on this ELement. + // Include any other namespaces that are inherited. + final Element pnt = getParentElement(); + if (pnt != null) { + for (Namespace ns : pnt.getNamespacesInScope()) { + if (!namespaces.containsKey(ns.getPrefix())) { + namespaces.put(ns.getPrefix(), ns); + } + } + } + + if (pnt == null && !namespaces.containsKey("")) { + // we are the root element, and there is no 'default' namespace. + namespaces.put(Namespace.NO_NAMESPACE.getPrefix(), Namespace.NO_NAMESPACE); + } + + ArrayList al = new ArrayList(namespaces.size()); + al.add(getNamespace()); + namespaces.remove(getNamespacePrefix()); + al.addAll(namespaces.values()); + + return Collections.unmodifiableList(al); + } + + @Override + public List getNamespacesInherited() { + if (getParentElement() == null) { + ArrayList ret = new ArrayList(getNamespacesInScope()); + for (Iterator it = ret.iterator(); it.hasNext();) { + Namespace ns = it.next(); + if (ns == Namespace.NO_NAMESPACE || ns == Namespace.XML_NAMESPACE) { + continue; + } + it.remove(); + } + return Collections.unmodifiableList(ret); + } + + // OK, the things we inherit are the prefixes we have in scope that + // are also in our parent's scope. + HashMap parents = new HashMap(); + for (Namespace ns : getParentElement().getNamespacesInScope()) { + parents.put(ns.getPrefix(), ns); + } + + ArrayList al = new ArrayList(); + for (Namespace ns : getNamespacesInScope()) { + if (ns == parents.get(ns.getPrefix())) { + // inherited + al.add(ns); + } + } + + return Collections.unmodifiableList(al); + } + + @Override + public List getNamespacesIntroduced() { + if (getParentElement() == null) { + // we introduce everything... except Namespace.XML_NAMESPACE + List ret = new ArrayList(getNamespacesInScope()); + for (Iterator it = ret.iterator(); it.hasNext(); ) { + Namespace ns = it.next(); + if (ns == Namespace.XML_NAMESPACE || ns == Namespace.NO_NAMESPACE) { + it.remove(); + } + } + return Collections.unmodifiableList(ret); + } + + // OK, the things we introduce are the prefixes we have in scope that + // are *not* in our parent's scope. + HashMap parents = new HashMap(); + for (Namespace ns : getParentElement().getNamespacesInScope()) { + parents.put(ns.getPrefix(), ns); + } + + ArrayList al = new ArrayList(); + for (Namespace ns : getNamespacesInScope()) { + if (!parents.containsKey(ns.getPrefix()) || ns != parents.get(ns.getPrefix())) { + // introduced + al.add(ns); + } + } + + return Collections.unmodifiableList(al); + } + + @Override + public Element detach() { + return (Element)super.detach(); + } + + @Override + public void canContainContent(Content child, int index, boolean replace) throws IllegalAddException { + if (child instanceof DocType) { + throw new IllegalAddException( + "A DocType is not allowed except at the document level"); + } + } + + /** + * Sort the contents of this Element using a mechanism that is safe for JDOM + * content. See the notes on {@link #sortContent(Filter, Comparator)} for + * how the algorithm works. + *

+ * {@link Collections#sort(List, Comparator)} is not appropriate for sorting + * the Lists returned from {@link Element#getContent()} because those are + * 'live' lists, and the Collections.sort() method uses an algorithm that + * adds the content in the new location before removing it from the old. + * That creates validation issues with content attempting to attach to a + * parent before detaching first. + *

+ * This method provides a safe means to conveniently sort the content. + * + * @param comparator The Comparator to use for the sorting. + */ + public void sortContent(Comparator comparator) { + content.sort(comparator); + } + + /** + * Sort the child Elements of this Element using a mechanism that is safe + * for JDOM content. Other child content will be unaffected. See the notes + * on {@link #sortContent(Filter, Comparator)} for how the algorithm works. + *

+ * {@link Collections#sort(List, Comparator)} is not appropriate for sorting + * the Lists returned from {@link Element#getContent()} because those are + * 'live' lists, and the Collections.sort() method uses an algorithm that + * adds the content in the new location before removing it from the old. + * This creates validation issues with content attempting to attach to a + * parent before detaching first. + *

+ * This method provides a safe means to conveniently sort the content. + * + * @param comparator The Comparator to use for the sorting. + */ + public void sortChildren(Comparator comparator) { + ((FilterList)getChildren()).sort(comparator); + } + + /** + * Sort the Attributes of this Element using a mechanism that is safe + * for JDOM. Other child content will be unaffected. See the notes + * on {@link #sortContent(Filter, Comparator)} for how the algorithm works. + *

+ * {@link Collections#sort(List, Comparator)} is not appropriate for sorting + * the Lists returned from {@link Element#getContent()} because those are + * 'live' lists, and the Collections.sort() method uses an algorithm that + * adds the content in the new location before removing it from the old. + * This creates validation issues with content attempting to attach to a + * parent before detaching first. + *

+ * This method provides a safe means to conveniently sort the content. + *

+ * A null comparator will sort the Attributes alphabetically first by prefix, + * then by name + * + * @param comparator The Comparator to use for the sorting. + */ + public void sortAttributes(Comparator comparator) { + if (attributes != null) { + attributes.sort(comparator); + } + } + + /** + * Sort the child content of this Element that matches the Filter, using a + * mechanism that is safe for JDOM content. Other child content (that does + * not match the filter) will be unaffected. + *

+ * The algorithm used for sorting affects the child content in the following + * ways: + *

    + *
  1. Items not matching the Filter will be unchanged. This includes the + * absolute position of that content in this Element. i.e. if child content + * cc does not match the Filter, then indexOf(cc) + * will not be changed by this sort. + *
  2. Items matching the Filter will be reordered according to the + * comparator. Only the relative order of the Filtered data will change. + *
  3. Items that compare as 'equals' using the comparator will keep the + * the same relative order as before the sort. + *
+ *

+ * {@link Collections#sort(List, Comparator)} is not appropriate for sorting + * the Lists returned from {@link Element#getContent()} because those are + * 'live' lists, and the Collections.sort() method uses an algorithm that + * adds the content in the new location before removing it from the old. + * This creates validation issues with content attempting to attach to a + * parent before detaching first. + *

+ * This method provides a safe means to conveniently sort the content. + * @param The generic type of the Filter used to select the content to + * sort. + * @param filter The Filter used to select which child content to sort. + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @param comparator The Comparator to use for the sorting. + */ + public void sortContent(Filter filter, Comparator comparator) { + final FilterList list = (FilterList)getContent(filter); + list.sort(comparator); + + } + + + /** + * Simple method that supports getXMLBaseURI(). + * @param uri 'currently' URI as a string + * @param relative the relative URI we are trying to make absolute + * @return the resulting URI, may be null. + * @throws URISyntaxException for URI problems. + */ + private final URI resolve(String uri, URI relative) + throws URISyntaxException { + if (uri == null) { + return relative; + } + final URI n = new URI(uri); + if (relative == null) { + return n; + } + return n.resolve(relative); + } + + /** + * Calculate the XMLBase URI for this Element using the rules defined in the + * XMLBase specification, as well as the values supplied in the xml:base + * attributes on this Element and its ancestry. + *

+ * This method assumes that all values in xml:base attributes + * are valid URI values according to the java.net.URI + * implementation. The same implementation is used to resolve relative URI + * values, and thus this code follows the assumptions in java.net.URI. + *

+ * This technically deviates from the XMLBase spec because to fully support + * legacy HTML the xml:base attribute could contain what is called a 'LIERI' + * which is a superset of true URI values, but for practical purposes JDOM + * users should never encounter such values because they are not processing + * raw HTML (but xhtml maybe). + * + * @return a URI representing the XMLBase value for the supplied Element, or + * null if one could not be calculated. + * @throws URISyntaxException + * if it is not possible to create java.net.URI values from the data + * in the xml:base attributes. + */ + public URI getXMLBaseURI() throws URISyntaxException { + Parent p = this; + URI ret = null; + while (p != null) { + if (p instanceof Element) { + ret = resolve(((Element) p).getAttributeValue("base", + Namespace.XML_NAMESPACE), ret); + } else { + ret = resolve(((Document) p).getBaseURI(), ret); + } + if (ret != null && ret.isAbsolute()) { + return ret; + } + p = p.getParent(); + } + return ret; + } + + + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + /** + * Serialize out the Element. + * + * @serialData + * The Stream protocol is: + *

    + *
  1. The Element name and Namespace using default Serialization. + *
  2. The count of additional Namespace Declarations. + *
  3. The actual additional Namespace Declarations. + *
  4. The count of Attributes. + *
  5. The actual Attributes. + *
  6. The count of child Content + *
  7. The actual Child Content. + *
+ * + * @param out where to write the Element to. + * @throws IOException if there is a writing problem. + */ + private void writeObject(final ObjectOutputStream out) throws IOException { + // sends out the name and namespace. + out.defaultWriteObject(); + if (hasAdditionalNamespaces()) { + final int ans = additionalNamespaces.size(); + out.writeInt(ans); + for (int i = 0; i < ans; i++) { + out.writeObject(additionalNamespaces.get(i)); + } + } else { + out.writeInt(0); + } + if (hasAttributes()) { + final int ans = attributes.size(); + out.writeInt(ans); + for (int i = 0; i < ans; i++) { + out.writeObject(attributes.get(i)); + } + } else { + out.writeInt(0); + } + + final int cs = content.size(); + out.writeInt(cs); + for (int i = 0; i < cs; i++) { + out.writeObject(content.get(i)); + } + + } + + /** + * Read an Element off the ObjectInputStream. + * + * @see #writeObject(ObjectOutputStream) + * @param in where to read the Element from. + * @throws IOException if there is a reading problem. + * @throws ClassNotFoundException when a class cannot be found + */ + private void readObject(final ObjectInputStream in) + throws IOException, ClassNotFoundException { + + in.defaultReadObject(); + + content = new ContentList(this); + + int nss = in.readInt(); + + while (--nss >= 0) { + addNamespaceDeclaration((Namespace)in.readObject()); + } + + int ats = in.readInt(); + while (--ats >= 0) { + setAttribute((Attribute)in.readObject()); + } + + int cs = in.readInt(); + while (--cs >= 0) { + addContent((Content)in.readObject()); + } + + } + +} diff --git a/core/src/java/org/jdom/EntityRef.java b/core/src/java/org/jdom/EntityRef.java new file mode 100644 index 0000000..a3f55b4 --- /dev/null +++ b/core/src/java/org/jdom/EntityRef.java @@ -0,0 +1,265 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * An XML entity reference. Methods allow the user to manage its name, public + * id, and system id. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Philip Nelson + * @author Rolf Lear + */ +public class EntityRef extends Content { + + /** + * JDOM2 Serialization. In this case, EntityRef is simple. + */ + private static final long serialVersionUID = 200L; + + /** The name of the EntityRef */ + protected String name; + + /** The PublicID of the EntityRef */ + protected String publicID; + + /** The SystemID of the EntityRef */ + protected String systemID; + + /** + * Default, no-args constructor for implementations to use if needed. + */ + protected EntityRef() { + super(CType.EntityRef); + } + + /** + * This will create a new EntityRef with the supplied name. + * + * @param name String name of element. + * @throws IllegalNameException if the given name is not a legal + * XML name. + */ + public EntityRef(String name) { + this(name, null, null); + } + + /** + * This will create a new EntityRef + * with the supplied name and system id. + * + * @param name String name of element. + * @param systemID system id of the entity reference being constructed + * @throws IllegalNameException if the given name is not a legal + * XML name. + * @throws IllegalDataException if the given system ID is not a legal + * system literal. + */ + public EntityRef(String name, String systemID) { + this(name, null, systemID); + } + + /** + * This will create a new EntityRef + * with the supplied name, public id, and system id. + * + * @param name String name of element. + * @param publicID public id of the entity reference being constructed + * @param systemID system id of the entity reference being constructed + * @throws IllegalDataException if the given system ID is not a legal + * system literal or the the given public ID is not a + * legal public ID + * @throws IllegalNameException if the given name is not a legal + * XML name. + */ + public EntityRef(String name, String publicID, String systemID) { + super(CType.EntityRef); + setName(name); + setPublicID(publicID); + setSystemID(systemID); + } + + /** + * This returns the name of the EntityRef. + * + * @return String - entity name. + */ + public String getName() { + return name; + } + + /** + * Returns the empty string since entity references don't have an XPath + * 1.0 string value. + * @return the empty string + */ + @Override + public String getValue() { + return ""; // entity references don't have XPath string values + } + + /** + * This will return the publid ID of this EntityRef. + * If there is no public ID, then this returns null. + * + * @return public ID of this EntityRef + */ + public String getPublicID() { + return publicID; + } + + /** + * This will return the system ID of this EntityRef. + * If there is no system ID, then this returns null. + * + * @return system ID of this EntityRef + */ + public String getSystemID() { + return systemID; + } + + /** + * This will set the name of this EntityRef. + * + * @param name new name of the entity + * @return this EntityRef modified. + * @throws IllegalNameException if the given name is not a legal + * XML name. + */ + public EntityRef setName(String name) { + // This can contain a colon so we use checkXMLName() + // instead of checkElementName() + String reason = Verifier.checkXMLName(name); + if (reason != null) { + throw new IllegalNameException(name, "EntityRef", reason); + } + this.name = name; + return this; + } + + /** + * This will set the public ID of this EntityRef. + * + * @param publicID new public id + * @return this EntityRef modified. + * @throws IllegalDataException if the given public ID is not a legal + * public ID. + */ + public EntityRef setPublicID(String publicID) { + String reason = Verifier.checkPublicID(publicID); + if (reason != null) { + throw new IllegalDataException(publicID, "EntityRef", reason); + } + this.publicID = publicID; + return this; + } + + /** + * This will set the system ID of this EntityRef. + * + * @param systemID new system id + * @throws IllegalDataException if the given system ID is not a legal + * system literal. + * @return this EntityRef modified. + */ + public EntityRef setSystemID(String systemID) { + String reason = Verifier.checkSystemLiteral(systemID); + if (reason != null) { + throw new IllegalDataException(systemID, "EntityRef", reason); + } + this.systemID = systemID; + return this; + } + + /** + * This returns a String representation of the + * EntityRef, suitable for debugging. + * + * @return String - information about the + * EntityRef + */ + @Override + public String toString() { + return new StringBuilder() + .append("[EntityRef: ") + .append("&") + .append(name) + .append(";") + .append("]") + .toString(); + } + + @Override + public EntityRef detach() { + return (EntityRef)super.detach(); + } + + @Override + protected EntityRef setParent(Parent parent) { + return (EntityRef)super.setParent(parent); + } + + @Override + public Element getParent() { + // because DocType can only be attached to a Document. + return (Element)super.getParent(); + } + + @Override + public EntityRef clone() { + return (EntityRef)super.clone(); + } +} diff --git a/core/src/java/org/jdom/FilterIterator.java b/core/src/java/org/jdom/FilterIterator.java new file mode 100644 index 0000000..6bede7d --- /dev/null +++ b/core/src/java/org/jdom/FilterIterator.java @@ -0,0 +1,134 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +import org.jdom.filter2.*; +import org.jdom.util.IteratorIterable; + +/** + * Traverse a parent's children that match the supplied filter. + * + * @author Bradley S. Huffman + * @author Rolf Lear + * @param The Generic type of content returned by this FilterIterator. + */ +final class FilterIterator implements IteratorIterable { + + private final DescendantIterator iterator; + private final Filter filter; + private T nextObject; + private boolean canremove = false; + + public FilterIterator(DescendantIterator iterator, Filter filter) { + // can trust that iterator is not null, but filter may be. + if (filter == null) { + throw new NullPointerException("Cannot specify a null Filter " + + "for a FilterIterator"); + } + this.iterator = iterator; + this.filter = filter; + } + + @Override + public Iterator iterator() { + return new FilterIterator(iterator.iterator(), filter); + } + + @Override + public boolean hasNext() { + + canremove = false; + + if (nextObject != null) { + return true; + } + + while (iterator.hasNext()) { + final Object obj = iterator.next(); + final T f = filter.filter(obj); + if (f != null) { + nextObject = f; + return true; + } + } + return false; + } + + @Override + public T next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final T obj = nextObject; + nextObject = null; + canremove = true; + return obj; + } + + @Override + public void remove() { + if (!canremove) { + throw new IllegalStateException("remove() can only be called " + + "on the FilterIterator immediately after a successful " + + "call to next(). A call to remove() immediately after " + + "a call to hasNext() or remove() will also fail."); + } + canremove = false; + iterator.remove(); + } +} diff --git a/core/src/java/org/jdom/IllegalAddException.java b/core/src/java/org/jdom/IllegalAddException.java new file mode 100644 index 0000000..fdaccc9 --- /dev/null +++ b/core/src/java/org/jdom/IllegalAddException.java @@ -0,0 +1,321 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * Thrown when trying to add an illegal object to a JDOM construct. + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public class IllegalAddException extends IllegalArgumentException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * This will create an Exception indicating + * that the addition of the {@link Attribute} + * to the {@link Element} is illegal. + * + * @param base Element that Attribute + * couldn't be added to + * @param added Attribute that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, Attribute added, String reason) { + super(new StringBuilder() + .append("The attribute \"") + .append(added.getQualifiedName()) + .append("\" could not be added to the element \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link Element} + * to parent is illegal. + * + * @param base Element that the child + * couldn't be added to + * @param added Element that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, Element added, String reason) { + super(new StringBuilder() + .append("The element \"") + .append(added.getQualifiedName()) + .append("\" could not be added as a child of \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link Element} + * to the {@link Document} is illegal. + * + * @param added Element that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element added, String reason) { + super(new StringBuilder() + .append("The element \"") + .append(added.getQualifiedName()) + .append("\" could not be added as the root of the document: ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link ProcessingInstruction} + * to the {@link Element} is illegal. + * + * @param base Element that the + * ProcessingInstruction couldn't be added to + * @param added ProcessingInstruction that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, ProcessingInstruction added, + String reason) { + super(new StringBuilder() + .append("The PI \"") + .append(added.getTarget()) + .append("\" could not be added as content to \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link ProcessingInstruction} + * to the {@link Document} is illegal. + * + * @param added ProcessingInstruction that could not be added + * @param reason cause of the problem + */ + IllegalAddException(ProcessingInstruction added, + String reason) { + super(new StringBuilder() + .append("The PI \"") + .append(added.getTarget()) + .append("\" could not be added to the top level of the document: ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link Comment} + * to the {@link Element} is illegal. + * + * @param base Element that the Comment + * couldn't be added to + * @param added Comment that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, Comment added, String reason) { + super(new StringBuilder() + .append("The comment \"") + .append(added.getText()) + .append("\" could not be added as content to \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + + /** + * This will create an Exception indicating + * that the addition of the {@link CDATA} + * + * @param base Element that the CDATA + * couldn't be added to + * @param added CDATA that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, CDATA added, String reason) { + super(new StringBuilder() + .append("The CDATA \"") + .append(added.getText()) + .append("\" could not be added as content to \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + + /** + * This will create an Exception indicating + * that the addition of the {@link Text} + * to the {@link Element} is illegal. + * + * @param base Element that the Comment + * couldn't be added to + * @param added Text that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, Text added, String reason) { + super(new StringBuilder() + .append("The Text \"") + .append(added.getText()) + .append("\" could not be added as content to \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link Comment} + * to the {@link Document} is illegal. + * + * @param added Comment that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Comment added, String reason) { + super(new StringBuilder() + .append("The comment \"") + .append(added.getText()) + .append("\" could not be added to the top level of the document: ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link EntityRef} + * to the {@link Element} is illegal. + * + * @param base Element that the EntityRef + * couldn't be added to + * @param added EntityRef reference that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, EntityRef added, String reason) { + super(new StringBuilder() + .append("The entity reference\"") + .append(added.getName()) + .append("\" could not be added as content to \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link Namespace} + * to the {@link Element} is illegal. + * + * @param base Element that the Namespace + * couldn't be added to + * @param added Namespace that could not be added + * @param reason cause of the problem + */ + IllegalAddException(Element base, Namespace added, String reason) { + super(new StringBuilder() + .append("The namespace xmlns") + .append(added.getPrefix().equals("") ? "=" + : ":" + added.getPrefix() + "=") + .append("\"") + .append(added.getURI()) + .append("\" could not be added as a namespace to \"") + .append(base.getQualifiedName()) + .append("\": ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception indicating + * that the addition of the {@link DocType} + * to the {@link Document} is illegal. + * + * @param added DocType that could not be added + * @param reason cause of the problem + */ + IllegalAddException(DocType added, String reason) { + super(new StringBuilder() + .append("The DOCTYPE ") + .append(added.toString()) + .append(" could not be added to the document: ") + .append(reason) + .toString()); + } + + /** + * This will create an Exception with the specified + * error message. + * + * @param reason cause of the problem + */ + public IllegalAddException(String reason) { + super(reason); + } +} diff --git a/core/src/java/org/jdom/IllegalDataException.java b/core/src/java/org/jdom/IllegalDataException.java new file mode 100644 index 0000000..f1354c2 --- /dev/null +++ b/core/src/java/org/jdom/IllegalDataException.java @@ -0,0 +1,117 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * Thrown when illegal text is supplied to a JDOM construct. + * + * @author Brett McLaughlin + * @author Elliotte Rusty Harold + */ +public class IllegalDataException extends IllegalArgumentException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * This will create an Exception indicating + * that the specified data is illegal for the construct + * it was supplied to. + * + * @param data String data that breaks rules. + * @param construct String construct that data is illegal for. + * @param reason String message or reason data is illegal. + */ + IllegalDataException(String data, String construct, String reason) { + super(new StringBuilder() + .append("The data \"") + .append(data) + .append("\" is not legal for a JDOM ") + .append(construct) + .append(": ") + .append(reason) + .append(".") + .toString()); + } + + /** + * This will create an Exception indicating + * that the specified data is illegal for the construct + * it was supplied to. + * + * @param data String data that breaks rules. + * @param construct String construct that data is illegal for. + */ + IllegalDataException(String data, String construct) { + super(new StringBuilder() + .append("The data \"") + .append(data) + .append("\" is not legal for a JDOM ") + .append(construct) + .append(".") + .toString()); + } + + /** + * This will create an exceptoin with the specified error message. + * + * @param reason cause of the problem + */ + public IllegalDataException(String reason) { + super(reason); + } +} diff --git a/core/src/java/org/jdom/IllegalNameException.java b/core/src/java/org/jdom/IllegalNameException.java new file mode 100644 index 0000000..48eb865 --- /dev/null +++ b/core/src/java/org/jdom/IllegalNameException.java @@ -0,0 +1,120 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * Thrown when a name is supplied in construction of a JDOM construct whose + * where the name breaks XML naming conventions. + * + * @author Brett McLaughlin + * @author Elliotte Rusty Harold + */ +public class IllegalNameException extends IllegalArgumentException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * This will create an Exception indicating + * that the specified name is illegal for the construct + * it was supplied to. + * + * @param name String name that breaks rules. + * @param construct String name of JDOM construct + * that name was supplied to. + * @param reason String message or reason name is illegal. + */ + IllegalNameException(String name, String construct, String reason) { + super(new StringBuilder() + .append("The name \"") + .append(name) + .append("\" is not legal for JDOM/XML ") + .append(construct) + .append("s: ") + .append(reason) + .append(".") + .toString()); + } + + /** + * This will create an Exception indicating + * that the specified name is illegal for the construct + * it was supplied to. + * + * @param name String name that breaks rules. + * @param construct String name of JDOM construct + * that name was supplied to. + */ + IllegalNameException(String name, String construct) { + super(new StringBuilder() + .append("The name \"") + .append(name) + .append("\" is not legal for JDOM/XML ") + .append(construct) + .append("s.") + .toString()); + } + + /** + * Creates an exception with the specified error message. + * + * @param reason cause of the problem + */ + public IllegalNameException(String reason) { + super(reason); + } +} diff --git a/core/src/java/org/jdom/IllegalTargetException.java b/core/src/java/org/jdom/IllegalTargetException.java new file mode 100644 index 0000000..ce944ab --- /dev/null +++ b/core/src/java/org/jdom/IllegalTargetException.java @@ -0,0 +1,96 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * Thrown when a target is supplied in construction of a JDOM {@link + * ProcessingInstruction}, and that name breaks XML naming conventions. + * + * @author Brett McLaughlin + */ +public class IllegalTargetException extends IllegalArgumentException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * This will create an Exception indicating + * that the specified target is illegal for the + * {@link ProcessingInstruction} it was supplied to. + * + * @param target String target that breaks rules. + * @param reason String message or reason target is illegal. + */ + IllegalTargetException(String target, String reason) { + super(new StringBuilder() + .append("The target \"") + .append(target) + .append("\" is not legal for JDOM/XML Processing Instructions: ") + .append(reason) + .append(".") + .toString()); + } + + /** + * Creates an exception with the specified error message. + * + * @param reason cause of the problem + */ + public IllegalTargetException(String reason) { + super(reason); + } +} diff --git a/core/src/java/org/jdom/JDOMConstants.java b/core/src/java/org/jdom/JDOMConstants.java new file mode 100644 index 0000000..7a7bd24 --- /dev/null +++ b/core/src/java/org/jdom/JDOMConstants.java @@ -0,0 +1,176 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import javax.xml.transform.TransformerFactory; + +import org.jdom.output.LineSeparator; +import org.jdom.transform.JDOMResult; +import org.jdom.transform.JDOMSource; +import org.jdom.xpath.XPathFactory; + + +/** + * A collection of constants that may be useful to JDOM users. + *

+ * JDOM attempts to make knowing these 'magic' constants unnecessary, but it is + * not always possible. In an attempt to make it easier though, common constants + * are defined here. This is not a comprehensive list of all the constants that + * may be useful when processing XML, but it should cover most of those occasions + * where JDOM does not automatically do the 'right thing'. + *

+ * Many of these constants are already referenced inside the JDOM code. + * + * @author Rolf Lear + * + */ +public final class JDOMConstants { + + /** + * Keep out of public reach. + */ + private JDOMConstants () { + // private default constructor. + } + + /* + * XML Namespace constants + */ + + /** Defined as {@value} */ + public static final String NS_PREFIX_DEFAULT = ""; + /** Defined as {@value} */ + public static final String NS_URI_DEFAULT = ""; + + /** Defined as {@value} */ + public static final String NS_PREFIX_XML = "xml"; + /** Defined as {@value} */ + public static final String NS_URI_XML = "http://www.w3.org/XML/1998/namespace"; + + /** Defined as {@value} */ + public static final String NS_PREFIX_XMLNS = "xmlns"; + /** Defined as {@value} */ + public static final String NS_URI_XMLNS = "http://www.w3.org/2000/xmlns/"; + + + /** Defined as {@value} */ + public static final String SAX_PROPERTY_DECLARATION_HANDLER = + "http://xml.org/sax/properties/declaration-handler"; + + /** Defined as {@value} */ + public static final String SAX_PROPERTY_DECLARATION_HANDLER_ALT = + "http://xml.org/sax/handlers/DeclHandler"; + + /** Defined as {@value} */ + public static final String SAX_PROPERTY_LEXICAL_HANDLER = + "http://xml.org/sax/properties/lexical-handler"; + + /** Defined as {@value} */ + public static final String SAX_PROPERTY_LEXICAL_HANDLER_ALT = + "http://xml.org/sax/handlers/LexicalHandler"; + + + + /** Defined as {@value} */ + public static final String SAX_FEATURE_EXTERNAL_ENT = + "http://xml.org/sax/features/external-general-entities"; + + /** Defined as {@value} */ + public static final String SAX_FEATURE_VALIDATION = + "http://xml.org/sax/features/validation"; + + /** Defined as {@value} */ + public static final String SAX_FEATURE_NAMESPACES = + "http://xml.org/sax/features/namespaces"; + + /** Defined as {@value} */ + public static final String SAX_FEATURE_NAMESPACE_PREFIXES = + "http://xml.org/sax/features/namespace-prefixes"; + + + /** + * Constant used to define the {@link JDOMSource} with JAXP. + * Defined as {@value} + * @see JDOMSource + */ + public static final String JDOM2_FEATURE_JDOMSOURCE = + "http://jdom.org/jdom2/transform/JDOMSource/feature"; + + /** + * Constant used to define the {@link JDOMResult} with {@link TransformerFactory}. + * Defined as {@value} + * @see JDOMResult + */ + public static final String JDOM2_FEATURE_JDOMRESULT = + "http://jdom.org/jdom2/transform/JDOMResult/feature"; + + /** + * System Property queried to obtain an alternate default XPathFactory. + * Defined as {@value} + * @see XPathFactory + */ + public static final String JDOM2_PROPERTY_XPATH_FACTORY = + "org.jdom.xpath.XPathFactory"; + + /** + * System Property queried to obtain an alternate default Line Separator. + *

+ * Defined as {@value} + * @see LineSeparator + */ + public static final String JDOM2_PROPERTY_LINE_SEPARATOR = + "org.jdom.output.LineSeparator"; + +} diff --git a/core/src/java/org/jdom/JDOMException.java b/core/src/java/org/jdom/JDOMException.java new file mode 100644 index 0000000..ad750f1 --- /dev/null +++ b/core/src/java/org/jdom/JDOMException.java @@ -0,0 +1,103 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +/** + * The top level 'checked' exception that JDOM classes can throw. JDOM does + * throw a number of unchecked exceptions, but all the checked exceptions are + * descendants of this class. + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public class JDOMException extends Exception { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * This will create an Exception. + */ + public JDOMException() { + super("Error occurred in JDOM application."); + } + + /** + * This will create an Exception with the given message. + * + * @param message String message indicating + * the problem that occurred. + */ + public JDOMException(String message) { + super(message); + } + + /** + * This will create an Exception with the given message + * and wrap another Exception. This is useful when + * the originating Exception should be held on to. + * + * @param message String message indicating + * the problem that occurred. + * @param cause Throwable that caused this + * to be thrown. + */ + public JDOMException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/core/src/java/org/jdom/JDOMFactory.java b/core/src/java/org/jdom/JDOMFactory.java new file mode 100644 index 0000000..df7bd9c --- /dev/null +++ b/core/src/java/org/jdom/JDOMFactory.java @@ -0,0 +1,649 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +/** + * An interface to be used by builders when constructing JDOM objects. The + * DefaultJDOMFactory creates the standard top-level JDOM classes + * (Element, Document, Comment, etc). Another implementation of this factory + * could be used to create custom classes. + * + * @author Ken Rune Holland + * @author Phil Nelson + * @author Bradley S. Huffman + * @author Rolf Lear + */ +public interface JDOMFactory { + + // **** constructing Attributes **** + + /** + *

+ * This will create a new Attribute with the + * specified (local) name and value, and in the provided + * {@link org.jdom.Namespace}. + *

+ * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param namespace Namespace of the new Attribute + * @return the created Attribute instance + */ + public Attribute attribute(String name, String value, Namespace namespace); + + /** + * This will create a new Attribute with the + * specified (local) name, value, and type, and in the provided + * {@link org.jdom.Namespace}. + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type int type for new attribute. + * @param namespace Namespace namespace for new attribute. + * @return the created Attribute instance + * @deprecated Use {@link #attribute(String, String, AttributeType, Namespace)} + */ + @Deprecated + public Attribute attribute(String name, String value, + int type, Namespace namespace); + + /** + * This will create a new Attribute with the + * specified (local) name, value, and type, and in the provided + * {@link org.jdom.Namespace}. + * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type AttributeType type for new attribute. + * @param namespace Namespace namespace for new attribute. + * @return the created Attribute instance + */ + public Attribute attribute(String name, String value, + AttributeType type, Namespace namespace); + + /** + * This will create a new Attribute with the + * specified (local) name and value, and does not place + * the attribute in a {@link org.jdom.Namespace}. + *

+ * Note: This actually explicitly puts the + * Attribute in the "empty" Namespace + * ({@link org.jdom.Namespace#NO_NAMESPACE}). + *

+ * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @return the created Attribute instance + */ + public Attribute attribute(String name, String value); + + /** + * This will create a new Attribute with the + * specified (local) name, value and type, and does not place + * the attribute in a {@link org.jdom.Namespace}. + *

+ * Note: This actually explicitly puts the + * Attribute in the "empty" Namespace + * ({@link org.jdom.Namespace#NO_NAMESPACE}). + *

+ * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type int type for new attribute. + * @return the created Attribute instance + * @deprecated use {@link #attribute(String, String, AttributeType)} + */ + @Deprecated + public Attribute attribute(String name, String value, int type); + + /** + * This will create a new Attribute with the + * specified (local) name, value and type, and does not place + * the attribute in a {@link org.jdom.Namespace}. + *

+ * Note: This actually explicitly puts the + * Attribute in the "empty" Namespace + * ({@link org.jdom.Namespace#NO_NAMESPACE}). + *

+ * + * @param name String name of Attribute. + * @param value String value for new attribute. + * @param type AttributeType type for new attribute. + * @return the created Attribute instance + */ + public Attribute attribute(String name, String value, AttributeType type); + + // **** constructing CDATA **** + + /** + * This creates the CDATA with the supplied text. + * + * @param str String content of CDATA. + * @return the created CDATA instance + */ + public CDATA cdata(String str); + + /** + * This creates the CDATA with the supplied text. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param str String content of CDATA. + * @return the created CDATA instance + * @since JDOM2 + */ + public CDATA cdata(int line, int col, String str); + + // **** constructing Text **** + + /** + * This creates the Text with the supplied text. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param str String content of Text. + * @return the created Text instance + * @since JDOM2 + */ + public Text text(int line, int col, String str); + + // **** constructing Text **** + + /** + * This creates the Text with the supplied text. + * + * @param str String content of Text. + * @return the created Text instance + */ + public Text text(String str); + + // **** constructing Comment **** + + /** + * This creates the comment with the supplied text. + * + * @param text String content of comment. + * @return the created Comment instance + */ + public Comment comment(String text); + + /** + * This creates the comment with the supplied text. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param text String content of comment. + * @return the created Comment instance + * @since JDOM2 + */ + public Comment comment(int line, int col, String text); + + // **** constructing DocType + + /** + * This will create the DocType with + * the specified element name and a reference to an + * external DTD. + * + * @param elementName String name of + * element being constrained. + * @param publicID String public ID of + * referenced DTD + * @param systemID String system ID of + * referenced DTD + * @return the created DocType instance + */ + public DocType docType(String elementName, + String publicID, String systemID); + + /** + * This will create the DocType with + * the specified element name and reference to an + * external DTD. + * + * @param elementName String name of + * element being constrained. + * @param systemID String system ID of + * referenced DTD + * @return the created DocType instance + */ + public DocType docType(String elementName, String systemID); + + /** + * This will create the DocType with + * the specified element name + * + * @param elementName String name of + * element being constrained. + * @return the created DocType instance + */ + public DocType docType(String elementName); + + /** + * This will create the DocType with + * the specified element name and a reference to an + * external DTD. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param elementName String name of + * element being constrained. + * @param publicID String public ID of + * referenced DTD + * @param systemID String system ID of + * referenced DTD + * @return the created DocType instance + * @since JDOM2 + */ + public DocType docType(int line, int col, String elementName, + String publicID, String systemID); + + /** + * This will create the DocType with + * the specified element name and reference to an + * external DTD. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param elementName String name of + * element being constrained. + * @param systemID String system ID of + * referenced DTD + * @return the created DocType instance + * @since JDOM2 + */ + public DocType docType(int line, int col, String elementName, String systemID); + + /** + * This will create the DocType with + * the specified element name + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param elementName String name of + * element being constrained. + * @return the created DocType instance + * @since JDOM2 + */ + public DocType docType(int line, int col, String elementName); + + // **** constructing Document + + /** + * This will create a new Document, + * with the supplied {@link org.jdom.Element} + * as the root element and the supplied + * {@link org.jdom.DocType} declaration. + * + * @param rootElement Element for document root. + * @param docType DocType declaration. + * @return the created Document instance + */ + public Document document(Element rootElement, DocType docType); + + /** + * This will create a new Document, + * with the supplied {@link org.jdom.Element} + * as the root element and the supplied + * {@link org.jdom.DocType} declaration. + * + * @param rootElement Element for document root. + * @param docType DocType declaration. + * @param baseURI the URI from which this doucment was loaded. + * @return the created Document instance + */ + public Document document(Element rootElement, DocType docType, String baseURI); + + /** + * This will create a new Document, + * with the supplied {@link org.jdom.Element} + * as the root element, and no {@link org.jdom.DocType} + * declaration. + * + * @param rootElement Element for document root + * @return the created Document instance + */ + public Document document(Element rootElement); + + // **** constructing Elements **** + + /** + * This will create a new Element + * with the supplied (local) name, and define + * the {@link org.jdom.Namespace} to be used. + * + * @param name String name of element. + * @param namespace Namespace to put element in. + * @return the created Element instance + */ + public Element element(String name, Namespace namespace); + + /** + * This will create an Element in no + * {@link org.jdom.Namespace}. + * + * @param name String name of element. + * @return the created Element instance + */ + public Element element(String name); + + /** + * This will create a new Element with + * the supplied (local) name, and specifies the URI + * of the {@link org.jdom.Namespace} the Element + * should be in, resulting it being unprefixed (in the default + * namespace). + * + * @param name String name of element. + * @param uri String URI for Namespace element + * should be in. + * @return the created Element instance + */ + public Element element(String name, String uri); + + /** + * This will create a new Element with + * the supplied (local) name, and specifies the prefix and URI + * of the {@link org.jdom.Namespace} the Element + * should be in. + * + * @param name String name of element. + * @param prefix the NamespacePrefic to use for this Element + * @param uri String URI for Namespace element + * should be in. + * @return the created Element instance + */ + public Element element(String name, String prefix, String uri); + + /** + * This will create a new Element + * with the supplied (local) name, and define + * the {@link org.jdom.Namespace} to be used. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @param namespace Namespace to put element in. + * @return the created Element instance + * @since JDOM2 + */ + public Element element(int line, int col, String name, Namespace namespace); + + /** + * This will create an Element in no + * {@link org.jdom.Namespace}. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @return the created Element instance + * @since JDOM2 + */ + public Element element(int line, int col, String name); + + /** + * This will create a new Element with + * the supplied (local) name, and specifies the URI + * of the {@link org.jdom.Namespace} the Element + * should be in, resulting it being unprefixed (in the default + * namespace). + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @param uri String URI for Namespace element + * should be in. + * @return the created Element instance + * @since JDOM2 + */ + public Element element(int line, int col, String name, String uri); + + /** + * This will create a new Element with + * the supplied (local) name, and specifies the prefix and URI + * of the {@link org.jdom.Namespace} the Element + * should be in. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @param prefix the NamespacePrefic to use for this Element + * @param uri String URI for Namespace element + * should be in. + * @return the created Element instance + * @since JDOM2 + */ + public Element element(int line, int col, String name, String prefix, String uri); + + // **** constructing ProcessingInstruction **** + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param target String target of PI. + * @param data Map data for PI, in + * name/value pairs + * @return the created ProcessingInstruction instance + */ + public ProcessingInstruction processingInstruction(String target, + Map data); + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param target String target of PI. + * @param data String data for PI. + * @return the created ProcessingInstruction instance + */ + public ProcessingInstruction processingInstruction(String target, + String data); + + /** + * This will create a new ProcessingInstruction + * with the specified target and no data. + * + * @param target String target of PI. + * @return the created ProcessingInstruction instance + */ + public ProcessingInstruction processingInstruction(String target); + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param target String target of PI. + * @param data Map data for PI, in + * name/value pairs + * @return the created ProcessingInstruction instance + * @since JDOM2 + */ + public ProcessingInstruction processingInstruction(int line, int col, String target, + Map data); + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param target String target of PI. + * @param data String data for PI. + * @return the created ProcessingInstruction instance + * @since JDOM2 + */ + public ProcessingInstruction processingInstruction(int line, int col, String target, + String data); + + /** + * This will create a new ProcessingInstruction + * with the specified target and no data. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param target String target of PI. + * @return the created ProcessingInstruction instance + * @since JDOM2 + */ + public ProcessingInstruction processingInstruction(int line, int col, String target); + + // **** constructing EntityRef **** + + /** + * This will create a new EntityRef + * with the supplied name. + * + * @param name String name of element. + * @return the created EntityRef instance + */ + public EntityRef entityRef(String name); + + /** + * This will create a new EntityRef + * with the supplied name, public ID, and system ID. + * + * @param name String name of element. + * @param publicID String public ID of element. + * @param systemID String system ID of element. + * @return the created EntityRef instance + */ + public EntityRef entityRef(String name, String publicID, String systemID); + + /** + * This will create a new EntityRef + * with the supplied name and system ID. + * + * @param name String name of element. + * @param systemID String system ID of element. + * @return the created EntityRef instance + */ + public EntityRef entityRef(String name, String systemID); + + /** + * This will create a new EntityRef + * with the supplied name. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @return the created EntityRef instance + * @since JDOM2 + */ + public EntityRef entityRef(int line, int col, String name); + + /** + * This will create a new EntityRef + * with the supplied name, public ID, and system ID. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @param publicID String public ID of element. + * @param systemID String system ID of element. + * @return the created EntityRef instance + * @since JDOM2 + */ + public EntityRef entityRef(int line, int col, String name, String publicID, String systemID); + + /** + * This will create a new EntityRef + * with the supplied name and system ID. + * + * @param line The line on which this content begins. + * @param col The column on the line at which this content begins. + * @param name String name of element. + * @param systemID String system ID of element. + * @return the created EntityRef instance + * @since JDOM2 + */ + public EntityRef entityRef(int line, int col, String name, String systemID); + + // ===================================================================== + // List manipulation + // ===================================================================== + + /** + * This will add the specified content to the specified parent instance + * @param parent The {@link Parent} to add the content to. + * @param content The {@link Content} to add + */ + public void addContent(Parent parent, Content content); + + /** + * Sets a specific Attribute on an Element + * @param element The {@link Element} to set the Attribute on + * @param a The {@link Attribute} to set + */ + public void setAttribute(Element element, Attribute a); + + /** + * Adds a namespace declaration to an Element + * @param element The {@link Element} to add the Namespace to + * @param additional The {@link Namespace} to add. + */ + public void addNamespaceDeclaration(Element element, Namespace additional); + + /** + * Sets the 'root' Element for a Document. + * @param doc The {@link Document} to set the Root Element of. + * @param root The {@link Element} to set as the root. + */ + public void setRoot(Document doc, Element root); +} diff --git a/core/src/java/org/jdom/Namespace.java b/core/src/java/org/jdom/Namespace.java new file mode 100644 index 0000000..cdf2377 --- /dev/null +++ b/core/src/java/org/jdom/Namespace.java @@ -0,0 +1,393 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import static org.jdom.JDOMConstants.*; + +import java.io.InvalidObjectException; +import java.io.Serializable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * An XML namespace representation, as well as a factory for creating XML + * namespace objects. All methods on Namespace (including + * {@link #getNamespace(String)} and {@link #getNamespace(String, String)}) + * are thread-safe. + * + *

+ * See {@link NamespaceAware} for additional notes on how Namespaces are + * 'in-scope' in JDOM content, and how those in-scope Namespaces are accessed. + * + * @see NamespaceAware + * @author Brett McLaughlin + * @author Elliotte Rusty Harold + * @author Jason Hunter + * @author Wesley Biggs + * @author Rolf Lear + */ +public final class Namespace implements Serializable { + + /** + * Factory list of namespaces. + * Keys are URI&prefix. + * Values are Namespace objects + */ + + private static final ConcurrentMap> + namespacemap = new ConcurrentHashMap + >(512, 0.75f, 64); + + /** Define a Namespace for when not in a namespace */ + public static final Namespace NO_NAMESPACE = new Namespace(NS_PREFIX_DEFAULT, + NS_URI_DEFAULT); + + /** Define a Namespace for the standard xml prefix. */ + public static final Namespace XML_NAMESPACE = new Namespace(NS_PREFIX_XML, + NS_URI_XML); + + private static final Namespace XMLNS_NAMESPACE = new Namespace(NS_PREFIX_XMLNS, + NS_URI_XMLNS); + + static { + // pre-populate the map with the constant namespaces that would + // otherwise fail validation + final ConcurrentMap nmap = + new ConcurrentHashMap(); + nmap.put(NO_NAMESPACE.getPrefix(), NO_NAMESPACE); + namespacemap.put(NO_NAMESPACE.getURI(), nmap); + + final ConcurrentMap xmap = + new ConcurrentHashMap(); + xmap.put(XML_NAMESPACE.getPrefix(), XML_NAMESPACE); + namespacemap.put(XML_NAMESPACE.getURI(), xmap); + + final ConcurrentMap xnsmap = + new ConcurrentHashMap(); + xnsmap.put(XMLNS_NAMESPACE.getPrefix(), XMLNS_NAMESPACE); + namespacemap.put(XMLNS_NAMESPACE.getURI(), xnsmap); + } + + /** + * This will retrieve (if in existence) or create (if not) a + * Namespace for the supplied prefix and uri. + * This method is thread-safe. + * + * @param prefix String prefix to map to + * Namespace. + * @param uri String URI of new Namespace. + * @return Namespace - ready to use namespace. + * @throws IllegalNameException if the given prefix and uri make up + * an illegal namespace name. + * @see Verifier#checkNamespacePrefix(String) + * @see Verifier#checkNamespaceURI(String) + */ + public static Namespace getNamespace(final String prefix, final String uri) { + + // This is a rewrite of the JDOM 1 getNamespace() to use + // java.util.concurrent. The motivation is: + // 1. avoid having to create a new NamespaceKey for each query. + // 2. avoid a 'big' synchronisation bottleneck in the Namespace class. + // 3. no-memory-lookup for pre-existing Namespaces... (avoid 'new' and + // most String methods that allocte memory (like trim()) + + if (uri == null) { + if (prefix == null || NS_PREFIX_DEFAULT.equals(prefix)) { + return NO_NAMESPACE; + } + // we have an attempt for some prefix + // (not "" or it would have found NO_NAMESPACE) on the null URI + throw new IllegalNameException("", "namespace", + "Namespace URIs must be non-null and non-empty Strings"); + } + + // must have checked for 'null' uri else namespacemap throws NPE + // do not 'trim' uri's any more see issue #50 + ConcurrentMap urimap = namespacemap.get(uri); + if (urimap == null) { + // no Namespaces yet with that URI. + // Ensure proper naming + String reason; + if ((reason = Verifier.checkNamespaceURI(uri)) != null) { + throw new IllegalNameException(uri, "Namespace URI", reason); + } + + urimap = new ConcurrentHashMap(); + final ConcurrentMap xmap = + namespacemap.putIfAbsent(uri, urimap); + + if (xmap != null) { + // some other thread registered this URI between when we + // first checked, and when we got a new map created. + // we must use the already-registered value. + urimap = xmap; + } + } + + // OK, we have a container for the URI, let's search on the prefix. + + Namespace ns = urimap.get(prefix == null ? NS_PREFIX_DEFAULT : prefix); + if (ns != null) { + // got one. + return ns; + } + + // OK, no namespace yet for that uri/prefix + // validate the prefix (the uri is already validated). + + if (NS_URI_DEFAULT.equals(uri)) { + // we have an attempt for some prefix + // (not "" or it would have found NO_NAMESPACE) on the "" URI + // note, we have already done this check for 'null' uri above. + throw new IllegalNameException("", "namespace", + "Namespace URIs must be non-null and non-empty Strings"); + } + + // http://www.w3.org/TR/REC-xml-names/#xmlReserved + // The erratum to Namespaces in XML 1.0 that suggests this + // next check is controversial. Not everyone accepts it. + if (NS_URI_XML.equals(uri)) { + throw new IllegalNameException(uri, "Namespace URI", + "The " + NS_URI_XML + " must be bound to " + + "only the '" + NS_PREFIX_XML + "' prefix."); + } + + // http://www.w3.org/TR/REC-xml-names/#xmlReserved + if (NS_URI_XMLNS.equals(uri)) { + throw new IllegalNameException(uri, "Namespace URI", + "The " + NS_URI_XMLNS + " must be bound to " + + "only the '" + NS_PREFIX_XMLNS + "' prefix."); + } + + // no namespace found, we validate the prefix + final String pfx = prefix == null ? NS_PREFIX_DEFAULT : prefix; + + String reason; + + // http://www.w3.org/TR/REC-xml-names/#xmlReserved + // checkNamespacePrefix no longer checks for xml prefix + if (NS_PREFIX_XML.equals(pfx)) { + // The xml namespace prefix was in the map. attempts to rebind it are illegal + throw new IllegalNameException(uri, "Namespace prefix", + "The prefix " + NS_PREFIX_XML + " (any case) can only be bound to " + + "only the '" + NS_URI_XML + "' uri."); + } + + // http://www.w3.org/TR/REC-xml-names/#xmlReserved + // checkNamespacePrefix no longer checks for xmlns prefix + if (NS_PREFIX_XMLNS.equals(pfx)) { + // The xml namespace prefix was in the map. attempts to rebind it are illegal + throw new IllegalNameException(uri, "Namespace prefix", + "The prefix " + NS_PREFIX_XMLNS + " (any case) can only be bound to " + + "only the '" + NS_URI_XMLNS + "' uri."); + } + + if ((reason = Verifier.checkNamespacePrefix(pfx)) != null) { + throw new IllegalNameException(pfx, "Namespace prefix", reason); + } + + // OK, good bet that we have a new Namespace. + ns = new Namespace(pfx, uri); + final Namespace prev = urimap.putIfAbsent(pfx, ns); + if (prev != null) { + // someone registered the same namespace as us while we were busy + // validating. Use their registered copy. + ns = prev; + } + return ns; + } + + /** The prefix mapped to this namespace */ + private final transient String prefix; + + /** The URI for this namespace */ + private final transient String uri; + + /** + * This will retrieve (if in existence) or create (if not) a + * Namespace for the supplied URI, and make it usable + * as a default namespace, as no prefix is supplied. + * This method is thread-safe. + * + * @param uri String URI of new Namespace. + * @return Namespace - ready to use namespace. + */ + public static Namespace getNamespace(final String uri) { + return getNamespace(NS_PREFIX_DEFAULT, uri); + } + + /** + * This constructor handles creation of a Namespace object + * with a prefix and URI; it is intentionally left private + * so that it cannot be invoked by external programs/code. + * + * @param prefix String prefix to map to this namespace. + * @param uri String URI for namespace. + */ + private Namespace(final String prefix, final String uri) { + this.prefix = prefix; + this.uri = uri; + } + + /** + * This returns the prefix mapped to this Namespace. + * + * @return String - prefix for this Namespace. + */ + public String getPrefix() { + return prefix; + } + + /** + * This returns the namespace URI for this Namespace. + * + * @return String - URI for this Namespace. + */ + public String getURI() { + return uri; + } + + /** + * This tests for equality - Two Namespaces + * are equal if and only if their URIs are byte-for-byte equals. + * + * @param ob Object to compare to this Namespace. + * @return boolean - whether the supplied object is equal to + * this Namespace. + */ + @Override + public boolean equals(final Object ob) { + if (this == ob) { + return true; + } + if (ob instanceof Namespace) { // instanceof returns false if null + return uri.equals(((Namespace)ob).uri); + } + return false; + } + + /** + * This returns a String representation of this + * Namespace, suitable for use in debugging. + * + * @return String - information about this instance. + */ + @Override + public String toString() { + return "[Namespace: prefix \"" + prefix + "\" is mapped to URI \"" + + uri + "\"]"; + } + + /** + * This returns the hash code for the Namespace that conforms + * to the 'equals()' contract. + *

+ * If two namespaces have the same URI, they are equal and have the same + * hash code, even if they have different prefixes. + * + * @return int - hash code for this Namespace. + */ + @Override + public int hashCode() { + return uri.hashCode(); + } + + + /* ***************************************** + * Serialization + * ***************************************** */ + + /** + * JDOM 2.0.0 Serialization version + */ + private static final long serialVersionUID = 200L; + + private static final class NamespaceSerializationProxy + implements Serializable { + + private static final long serialVersionUID = 200L; + private final String pprefix, puri; + public NamespaceSerializationProxy(String pprefix, String puri) { + this.pprefix = pprefix; + this.puri = puri; + } + + private Object readResolve() { + return Namespace.getNamespace(pprefix, puri); + } + + } + + /** + * Serializes Namespace by using a proxy serialization instance. + * @serialData The proxy deals with the protocol. + * @return the Namespace proxy instance. + */ + private Object writeReplace() { + return new NamespaceSerializationProxy(prefix, uri); + } + + /** + * Because Namespace is serialized by proxy, the reading of direct Namespace + * instances is illegal and prohibited. + * @return nothing. + * @throws InvalidObjectException always + */ + private Object readResolve() throws InvalidObjectException { + throw new InvalidObjectException( + "Namespace is serialized through a proxy"); + } + +} diff --git a/core/src/java/org/jdom/NamespaceAware.java b/core/src/java/org/jdom/NamespaceAware.java new file mode 100644 index 0000000..10c6575 --- /dev/null +++ b/core/src/java/org/jdom/NamespaceAware.java @@ -0,0 +1,210 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.List; + +/** + * Classes implementing this interface are all sensitive to their + * {@link Namespace} context. All the core JDOM classes are NamespaceAware + * ({@link Parent} and subtypes, {@link Content} and subtypes, and + * {@link Attribute}). You can use the methods that this interface provides + * to query the Namespace context. + *

+ * JDOM2 introduces a consistency in reporting Namespace context. XML standards + * do not dictate any conditions on Namespace reporting or ordering, but + * consistency is valuable for user-friendliness. As a result JDOM2 imposes a + * useful order on the Namespace context for XML content. + *

+ * The order for Namespace reporting is: + *

    + *
  1. If the item 'has' a Namespace (Element, Attribute) then that Namespace is + * reported. + *
  2. The remaining Namespaces are reported in alphabetical order by prefix. + *
+ *

+ * The XML namespace (bound to the prefix "xml" - see + * {@link Namespace#XML_NAMESPACE} ) is always in every scope. It is always + * introduced in {@link Document}, and in all other NamespaceAware instances it + * is introduced if that content is detached. + *

+ * See the individualised documentation for each implementing type for + * additional specific details. The following section is a description of how + * Namespaces are managed in the Element class. + *

+ *

The Element Namespace Scope

+ * The 'default' Namespace is a source of confusion, but it is simply the + * Namespace which is in-scope for an Element that has no Namespace prefix + * (prefix is "" but it could have any Namespace URI). There will always be + * exactly one Namespace that is in-scope for an element that has no prefix. + *

+ * All Elements are in a Namespace. Elements will be in + * {@link Namespace#NO_NAMESPACE} unless a different Namespace was supplied as + * part of the Element Constructor, or later modified by the + * {@link Element#setNamespace(Namespace)} method. + *

+ * In addition to the Element's Namespace, there could be other Namespaces that + * are 'in scope' for the Element. The set of Namespaces that are in scope for + * an Element is the union of five sets: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
XML + * There is always exactly one member of this set, + * {@link Namespace#XML_NAMESPACE}. + *
+ * This set cannot be changed. + *
Element + * There is always exactly one member of this set, and it can be retrieved + * or set with the methods {@link Element#getNamespace()} and + * {@link Element#setNamespace(Namespace)} respectively. + *
Attribute + * This is the set of distinct Namespaces that are used on Attributes. You + * can modify the set by adding and removing Attributes to the Element. + *

+ * NOTE: + * The {@link Namespace#NO_NAMESPACE Namespace.NO_NAMESPACE} Namespace is always the + * default Namespace for attributes (the Namespace that has no + * prefix). Thus there may be a special case with this Namespace, because + * if there is a different default Namespace for the Element, then + * the Namespace.NO_NAMESPACE Namespace is not part of the Element's in-scope + * Namespace set (the Element cannot have two Namespaces in scope with the + * same prefix - ""). + *

Additional + * This set is maintained by the two methods {@link Element#addNamespaceDeclaration(Namespace)} + * and {@link Element#removeNamespaceDeclaration(Namespace)}. You can get the full set + * of additional Namespaces with {@link Element#getAdditionalNamespaces()} + *
Inherited + * This last set is somewhat dynamic because only those Namespaces on the + * parent Element which are not re-defined by this Element will be + * inherited. A Namespace is redefined by setting a new Namespace with the + * same prefix, but a different URI. If you set a Namespace on the Element + * (or add a Namespace declaration or set an Attribute) with the same + * prefix as another Namespace that would have been otherwise inherited, + * then that other Namespace will no longer be inherited. + *
+ * + *

+ * Since you cannot change the Namespace.XML_NAMESPACE, and the 'inherited' Namespace set + * is dynamic, the remaining Namespace sets are the most interesting from a JDOM + * perspective. JDOM validates all modifications that affect the Namespaces in + * scope for an Element. An IllegalAddException will be thrown if you attempt to + * add a new Namespace to the in-scope set if a different Namespace with the + * same prefix is already part of one of these three sets (Element, Attribute, + * or Additional). + * + * + * @author Rolf Lear + * @since JDOM2 + */ +public interface NamespaceAware { + + /** + * Obtain a list of all namespaces that are in scope for the current + * content. + *

+ * The contents of this list will always be the combination of + * getNamespacesIntroduced() and getNamespacesInherited(). + *

+ * See {@link NamespaceAware} documentation for details on what the order of the + * Namespaces will be in the returned list. + * + * @return a read-only list of Namespaces. + */ + public List getNamespacesInScope(); + + /** + * Obtain a list of all namespaces that are introduced to the XML tree by + * this node. Only Elements and Attributes can introduce namespaces, so all + * other Content types will return an empty list. + *

+ * The contents of this list will always be a subset (but in the same order) + * of getNamespacesInScope(), and will never intersect + * getNamspacesInherited() + * + * @return a read-only list of Namespaces. + */ + public List getNamespacesIntroduced(); + + /** + * Obtain a list of all namespaces that are in scope for this content, but + * were not introduced by this content. + *

+ * The contents of this list will always be a subset (but in the same order) + * of getNamespacesInScope(), and will never intersect + * getNamspacesIntroduced() + * + * @return a read-only list of Namespaces. + */ + public List getNamespacesInherited(); +} diff --git a/core/src/java/org/jdom/Parent.java b/core/src/java/org/jdom/Parent.java new file mode 100644 index 0000000..70c25a6 --- /dev/null +++ b/core/src/java/org/jdom/Parent.java @@ -0,0 +1,346 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.jdom.filter.Filter; + +import java.io.Serializable; +import java.util.*; + +/** + * Interface for JDOM objects which are allowed to contain + * {@link Content} content - {@link Element} and {@link Document}. + * + * @see org.jdom.Content + * @see org.jdom.Document + * @see org.jdom.Element + * + * @author Bradley S. Huffman + * @author Jason Hunter + * @author Rolf Lear + */ +public interface Parent extends Cloneable, NamespaceAware, Serializable { + + /** + * Returns the number of children in this parent's content list. + * Children may be any {@link Content} type. + * + * @return number of children + */ + int getContentSize(); + + /** + * Returns the index of the supplied child in the content list, + * or -1 if not a child of this parent. + * + * @param child child to search for + * @return index of child, or -1 if not found + */ + int indexOf(Content child); + + // /** + // * Starting at the given index (inclusive), returns the index of + // * the first child matching the supplied filter, or -1 + // * if none is found. + // * + // * @return index of child, or -1 if none found + // */ + // int indexOf(int index, Filter filter); + + /** + * Returns a list containing detached clones of this parent's content list. + * + * @return list of cloned child content + */ + List cloneContent(); + + /** + * Returns the child at the given index. + * + * @param index location of desired child + * @return child at the given index + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalStateException if parent is a Document + * and the root element is not set + */ + Content getContent(int index); + + /** + * Returns the full content of this parent as a {@link java.util.List} + * which contains objects of type {@link Content}. The returned list is + * "live" and in document order. Any modifications + * to it affect the element's actual contents. Modifications are checked + * for conformance to XML 1.0 rules. + *

+ * Sequential traversal through the List is best done with an Iterator + * since the underlying implement of {@link java.util.List#size} may + * require walking the entire list and indexed lookups may require + * starting at the beginning each time. + * + * @return a list of the content of the parent + * @throws IllegalStateException if parent is a Document + * and the root element is not set + */ + List getContent(); + + /** + * Returns as a {@link java.util.List} the content of + * this parent that matches the supplied filter. The returned list is + * "live" and in document order. Any modifications to it affect + * the element's actual contents. Modifications are checked for + * conformance to XML 1.0 rules. + *

+ * Sequential traversal through the List is best done with an Iterator + * since the underlying implement of {@link java.util.List#size} may + * require walking the entire list and indexed lookups may require + * starting at the beginning each time. + * @param The Generic type of the returned content (the Filter's type) + * + * @param filter filter to apply. + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return a list of the content of the parent matching the filter + * @throws IllegalStateException if parent is a Document + * and the root element is not set + */ + List getContent(Filter filter); + + /** + * Removes all content from this parent and returns the detached + * children. + * + * @return list of the old content detached from this parent + */ + List removeContent(); + + /** + * Removes from this parent all child content matching the given filter + * and returns a list of the detached children. + * @param The Generic type of the content to remove. + * + * @param filter filter to apply + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return list of the detached children matching the filter + */ + List removeContent(Filter filter); + + /** + * Removes a single child node from the content list. + * + * @param child child to remove + * @return whether the removal occurred + */ + boolean removeContent(Content child); + + /** + * Removes and returns the child at the given + * index, or returns null if there's no such child. + * + * @param index index of child to remove + * @return detached child at given index or null if no + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + */ + Content removeContent(int index); + + /** + * Obtain a deep, unattached copy of this parent and it's children. + * + * @return a deep copy of this parent and it's children. + */ + Parent clone(); + + /** + * Returns an {@link java.util.Iterator} that walks over all descendants + * in document order. + *

+ * Note that this method returns an IteratorIterable instance, which means + * that you can use it either as an Iterator, or an Iterable, allowing both: + *

+ *

+	 *   for (Iterator it = parent.getDescendants();
+	 *           it.hasNext();) {
+	 *       Content c = it.next();
+	 *       ....
+	 *   }
+	 * 
+ * and + *
+	 *   for (Content c : parent.getDescendants()) {
+	 *       ....
+	 *   }
+	 * 
+ * The Iterator version is most useful if you need to do list modification + * on the iterator (using remove()), and for compatibility with JDOM 1.x + * + * @return an iterator to walk descendants + */ + Iterator getDescendants(); + + /** + * Returns an {@link java.util.Iterator} that walks over all descendants + * in document order applying the Filter to return only content that + * match the filter rule. With filters you can match only Elements, + * only Comments, Elements or Comments, only Elements with a given name + * and/or prefix, and so on. + *

+ * Note that this method returns an IteratorIterable instance, which means + * that you can use it either as an Iterator, or an Iterable, allowing both: + *

+ *

+	 *   for (Iterator it = parent.getDescendants(Filters.element());
+	 *           it.hasNext();) {
+	 *       Element e = it.next();
+	 *       ....
+	 *   }
+	 * 
+ * and + *
+	 *   for (Element e : parent.getDescendants(Filters.element())) {
+	 *       ....
+	 *   }
+	 * 
+ * The Iterator version is most useful if you need to do list modification + * on the iterator (using remove()), and for compatibility with JDOM 1.x + * + * @param The generic type of the returned descendant data + * @param filter filter to select which descendants to see + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return an iterator to walk descendants that match a filter + */ + Iterator getDescendants(Filter filter); + + /** + * Return this parent's parent, or null if this parent is currently + * not attached to another parent. This is the same method as in Content but + * also added to Parent to allow more easy up-the-tree walking. + * + * @return this parent's parent or null if none + */ + Parent getParent(); + + /** + * Return this parent's owning document or null if the branch containing + * this parent is currently not attached to a document. + * + * @return this child's owning document or null if none + */ + Document getDocument(); + + /** + * Test whether this Parent instance can contain the specified content + * at the specified position. + * @param content The content to be checked + * @param index The location where the content would be put. + * @param replace true if the intention is to replace the content already at + * the index. + * @throws IllegalAddException if there is a problem with the content + */ + void canContainContent(Content content, int index, boolean replace) throws IllegalAddException; + + /** + * Appends the child to the end of the content list. + * + * @param child child to append to end of content list + * @return the Parent instance on which the method was called + * @throws IllegalAddException if the given child already has a parent. + */ + public Parent addContent(Content child); + + /** + * Appends all children in the given collection to the end of + * the content list. In event of an exception during add the + * original content will be unchanged and the objects in the supplied + * collection will be unaltered. + * + * @param c collection to append + * @return the document on which the method was called + * @throws IllegalAddException if any item in the collection + * already has a parent or is of an illegal type. + */ + public Parent addContent(Collection c); + + /** + * Inserts the child into the content list at the given index. + * + * @param index location for adding the collection + * @param child child to insert + * @return the parent on which the method was called + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalAddException if the given child already has a parent. + */ + public Parent addContent(int index, Content child); + + /** + * Inserts the content in a collection into the content list + * at the given index. In event of an exception the original content + * will be unchanged and the objects in the supplied collection will be + * unaltered. + * + * @param index location for adding the collection + * @param c collection to insert + * @return the parent on which the method was called + * @throws IndexOutOfBoundsException if index is negative or beyond + * the current number of children + * @throws IllegalAddException if any item in the collection + * already has a parent or is of an illegal type. + */ + public Parent addContent(int index, Collection c); + +} diff --git a/core/src/java/org/jdom/ProcessingInstruction.java b/core/src/java/org/jdom/ProcessingInstruction.java new file mode 100644 index 0000000..74cbdd2 --- /dev/null +++ b/core/src/java/org/jdom/ProcessingInstruction.java @@ -0,0 +1,488 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.jdom.output.XMLOutputter2; + +import java.util.*; + +/** + * An XML processing instruction. Methods allow the user to obtain the target of + * the PI as well as its data. The data can always be accessed as a String or, + * if the data appears akin to an attribute list, can be retrieved as name/value + * pairs. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Steven Gould + */ + +public class ProcessingInstruction extends Content { + + /** + * JDOM2 Serialization. In this case, ProcessingInstruction is simple. + */ + private static final long serialVersionUID = 200L; + + /** The target of the PI */ + protected String target; + + /** The data for the PI as a String */ + protected String rawData; + + /** The data for the PI in name/value pairs */ + protected transient Map mapData = null; + + /** + * Default, no-args constructor for implementations + * to use if needed. + */ + protected ProcessingInstruction() { + super(CType.ProcessingInstruction); + } + + /** + * This will create a new ProcessingInstruction + * with the specified target. + * + * @param target String target of PI. + * @throws IllegalTargetException if the given target is illegal + * as a processing instruction name. + */ + public ProcessingInstruction(String target) { + this(target, ""); + } + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param target String target of PI. + * @param data Map data for PI, in + * name/value pairs + * @throws IllegalTargetException if the given target is illegal + * as a processing instruction name. + */ + public ProcessingInstruction(String target, Map data) { + super(CType.ProcessingInstruction); + setTarget(target); + setData(data); + } + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param target String target of PI. + * @param data String data for PI. + * @throws IllegalTargetException if the given target is illegal + * as a processing instruction name. + */ + public ProcessingInstruction(String target, String data) { + super(CType.ProcessingInstruction); + setTarget(target); + setData(data); + } + + /** + * This will set the target for the PI. + * + * @param newTarget String new target of PI. + * @return ProcessingInstruction - this PI modified. + */ + public ProcessingInstruction setTarget(String newTarget) { + String reason; + if ((reason = Verifier.checkProcessingInstructionTarget(newTarget)) + != null) { + throw new IllegalTargetException(newTarget, reason); + } + + target = newTarget; + return this; + } + + /** + * Returns the XPath 1.0 string value of this element, which is the + * data of this PI. + * + * @return the data of this PI + */ + @Override + public String getValue() { + return rawData; + } + + + /** + * This will retrieve the target of the PI. + * + * @return String - target of PI. + */ + public String getTarget() { + return target; + } + + /** + * This will return the raw data from all instructions. + * + * @return String - data of PI. + */ + public String getData() { + return rawData; + } + + /** + * This will return a List containing the names of the + * "attribute" style pieces of name/value pairs in this PI's data. + * + * @return List - the List containing the + * "attribute" names. + */ + public List getPseudoAttributeNames() { + return new ArrayList(mapData.keySet()); + } + + /** + * This will set the raw data for the PI. + * + * @param data String data of PI. + * @return ProcessingInstruction - this PI modified. + */ + public ProcessingInstruction setData(String data) { + String reason = Verifier.checkProcessingInstructionData(data); + if (reason != null) { + throw new IllegalDataException(data, reason); + } + + this.rawData = data; + this.mapData = parseData(data); + return this; + } + + /** + * This will set the name/value pairs within the passed + * Map as the pairs for the data of + * this PI. The keys should be the pair name + * and the values should be the pair values. + * + * @param data new map data to use + * @return ProcessingInstruction - modified PI. + */ + public ProcessingInstruction setData(Map data) { + String temp = toString(data); + + String reason = Verifier.checkProcessingInstructionData(temp); + if (reason != null) { + throw new IllegalDataException(temp, reason); + } + + this.rawData = temp; + this.mapData = new LinkedHashMap(data); + return this; + } + + + /** + * This will return the value for a specific + * name/value pair on the PI. If no such pair is + * found for this PI, null is returned. + * + * @param name String name of name/value pair + * to lookup value for. + * @return String - value of name/value pair. + */ + public String getPseudoAttributeValue(String name) { + return mapData.get(name); + } + + /** + * This will set a pseudo attribute with the given name and value. + * If the PI data is not already in a pseudo-attribute format, this will + * replace the existing data. + * + * @param name String name of pair. + * @param value String value for pair. + * @return ProcessingInstruction this PI modified. + */ + public ProcessingInstruction setPseudoAttribute(String name, String value) { + String reason = Verifier.checkProcessingInstructionData(name); + if (reason != null) { + throw new IllegalDataException(name, reason); + } + + reason = Verifier.checkProcessingInstructionData(value); + if (reason != null) { + throw new IllegalDataException(value, reason); + } + + this.mapData.put(name, value); + this.rawData = toString(mapData); + return this; + } + + + /** + * This will remove the pseudo attribute with the specified name. + * + * @param name name of pseudo attribute to remove + * @return boolean - whether the requested + * instruction was removed. + */ + public boolean removePseudoAttribute(String name) { + if ((mapData.remove(name)) != null) { + rawData = toString(mapData); + return true; + } + + return false; + } + + /** + * This will convert the Map to a string representation. + * + * @param pmapData Map PI data to convert + * @return a string representation of the Map as appropriate for a PI + */ + private static final String toString(Map pmapData) { + StringBuilder stringData = new StringBuilder(); + + for (Map.Entry me : pmapData.entrySet()) { + stringData.append(me.getKey()) + .append("=\"") + .append(me.getValue()) + .append("\" "); + } + // Remove last space, if we did any appending + if (stringData.length() > 0) { + stringData.setLength(stringData.length() - 1); + } + + return stringData.toString(); + } + + /** + * This will parse and load the instructions for the PI. + * This is separated to allow it to occur once and then be reused. + */ + private Map parseData(String prawData) { + // The parsing here is done largely "by hand" which means the code + // gets a little tricky/messy. The following conditions should + // now be handled correctly: + // Reads OK + // Reads OK + // Reads OK + // Reads OK + // Empty Map + // Empty Map + // Empty Map + + Map data = new LinkedHashMap(); + + // System.out.println("rawData: " + rawData); + + // The inputData variable holds the part of rawData left to parse + String inputData = prawData.trim(); + + // Iterate through the remaining inputData string + while (!inputData.trim().equals("")) { + //System.out.println("parseData() looking at: " + inputData); + + // Search for "name =", "name=" or "name1 name2..." + String name = ""; + String value = ""; + int startName = 0; + char previousChar = inputData.charAt(startName); + int pos = 1; + for (; pos 0) { + //if (data.containsKey(name)) { + // A repeat, that's a parse error, so return a null map + //return new HashMap(); + //} + //else { + data.put(name, value); + //} + } + } + + return data; + } + + /** + * This is a helper routine, only used by parseData, to extract a + * quoted String from the input parameter, rawData. A quoted string + * can use either single or double quotes, but they must match up. + * A singly quoted string can contain an unbalanced amount of double + * quotes, or vice versa. For example, the String "JDOM's the best" + * is legal as is 'JDOM"s the best'. + * + * @param rawData the input string from which a quoted string is to + * be extracted. + * @return the first quoted string encountered in the input data. If + * no quoted string is found, then the empty string, "", is + * returned. + * @see #parseData + */ + private static int[] extractQuotedString(String rawData) { + // Remembers whether we're actually in a quoted string yet + boolean inQuotes = false; + + // Remembers which type of quoted string we're in + char quoteChar = '"'; + + // Stores the position of the first character inside + // the quoted string (i.e. the start of the return string) + int start = 0; + + // Iterate through the input string looking for the start + // and end of the quoted string + for (int pos=0; pos < rawData.length(); pos++) { + char currentChar = rawData.charAt(pos); + if (currentChar=='"' || currentChar=='\'') { + if (!inQuotes) { + // We're entering a quoted string + quoteChar = currentChar; + inQuotes = true; + start = pos+1; + } + else if (quoteChar == currentChar) { + // We're leaving a quoted string + inQuotes = false; + return new int[] { start, pos }; + } + // Otherwise we've encountered a quote + // inside a quote, so just continue + } + } + + return null; + } + + /** + * This returns a String representation of the + * ProcessingInstruction, suitable for debugging. If the XML + * representation of the ProcessingInstruction is desired, + * {@link XMLOutputter2#outputString(ProcessingInstruction)} + * should be used. + * + * @return String - information about the + * ProcessingInstruction + */ + @Override + public String toString() { + return new StringBuilder() + .append("[ProcessingInstruction: ") + .append(new XMLOutputter2().outputString(this)) + .append("]") + .toString(); + } + + @Override + public ProcessingInstruction clone() { + ProcessingInstruction pi = (ProcessingInstruction) super.clone(); + + // target and rawdata are immutable and references copied by + // Object.clone() + + // Create a new Map object for the clone (since Map isn't Cloneable) + pi.mapData = parseData(rawData); + return pi; + } + + @Override + public ProcessingInstruction detach() { + return (ProcessingInstruction)super.detach(); + } + + @Override + protected ProcessingInstruction setParent(Parent parent) { + return (ProcessingInstruction)super.setParent(parent); + } + + +} diff --git a/core/src/java/org/jdom/SlimJDOMFactory.java b/core/src/java/org/jdom/SlimJDOMFactory.java new file mode 100644 index 0000000..4758377 --- /dev/null +++ b/core/src/java/org/jdom/SlimJDOMFactory.java @@ -0,0 +1,228 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.Map; + + +/** + * This JDOMFactory instance reduces the amount of memory used by JDOM content. + * It does this by reusing String instances instead of using new (but equals()) + * instances. It uses the {@link StringBin} class to provide a String cache. + * + * @see StringBin + * @author Rolf Lear + * + */ +public class SlimJDOMFactory extends DefaultJDOMFactory { + + private StringBin cache = new StringBin(); + private final boolean cachetext; + + /** + * + */ + public SlimJDOMFactory() { + this(true); + } + + /** + * Construct a SlimJDOMFactory which will optionally cache Text/CDATA/Comment/Attribute + * values. Caching these values is recommended because often XML documents have + * many instances of the same Text values (especially whitespace sequences...) + * @param cachetext should be true if you want the content of CDATA, Text, + * Comment and Attribute values cached as well. + */ + public SlimJDOMFactory(final boolean cachetext) { + super(); + this.cachetext = cachetext; + } + + + /** + * Reset any Cached String instance data from this SlimJDOMFaxctory cache. + */ + public void clearCache() { + cache = new StringBin(); + } + + @Override + public Attribute attribute(final String name, final String value, final Namespace namespace) { + return super.attribute(cache.reuse(name), + (cachetext ? cache.reuse(value) : value), + namespace); + } + + @Override + @Deprecated + public Attribute attribute(final String name, final String value, final int type, + final Namespace namespace) { + return super.attribute(cache.reuse(name), + (cachetext ? cache.reuse(value) : value), + type, namespace); + } + + @Override + public Attribute attribute(final String name, final String value, final AttributeType type, + Namespace namespace) { + return super.attribute(cache.reuse(name), + (cachetext ? cache.reuse(value) : value), + type, namespace); + } + + @Override + public Attribute attribute(final String name, final String value) { + return super.attribute(cache.reuse(name), + (cachetext ? cache.reuse(value) : value)); + } + + @Override + @Deprecated + public Attribute attribute(final String name, final String value, final int type) { + return super.attribute(cache.reuse(name), + (cachetext ? cache.reuse(value) : value), + type); + } + + @Override + public Attribute attribute(final String name, final String value, final AttributeType type) { + return super.attribute(cache.reuse(name), + (cachetext ? cache.reuse(value) : value), + type); + } + + @Override + public CDATA cdata(final int line, final int col, final String str) { + return super.cdata(line, col, (cachetext ? cache.reuse(str) : str)); + } + + @Override + public Text text(final int line, final int col, final String str) { + return super.text(line, col, (cachetext ? cache.reuse(str) : str)); + } + + @Override + public Comment comment(final int line, final int col, final String text) { + return super.comment(line, col, (cachetext ? cache.reuse(text) : text)); + } + + @Override + public DocType docType(final int line, final int col, final String elementName, final String publicID, final String systemID) { + return super.docType(line, col, cache.reuse(elementName), publicID, systemID); + } + + @Override + public DocType docType(final int line, final int col, final String elementName, final String systemID) { + return super.docType(line, col, cache.reuse(elementName), systemID); + } + + @Override + public DocType docType(final int line, final int col, final String elementName) { + return super.docType(line, col, cache.reuse(elementName)); + } + + @Override + public Element element(final int line, final int col, final String name, final Namespace namespace) { + return super.element(line, col, cache.reuse(name), namespace); + } + + @Override + public Element element(final int line, final int col, final String name) { + return super.element(line, col, cache.reuse(name)); + } + + @Override + public Element element(final int line, final int col, final String name, final String uri) { + return super.element(line, col, cache.reuse(name), uri); + } + + @Override + public Element element(final int line, final int col, final String name, final String prefix, final String uri) { + return super.element(line, col, cache.reuse(name), prefix, uri); + } + + @Override + public ProcessingInstruction processingInstruction(final int line, final int col, final String target, + final Map data) { + return super.processingInstruction(line, col, cache.reuse(target), data); + } + + @Override + public ProcessingInstruction processingInstruction(final int line, final int col, final String target, + final String data) { + return super.processingInstruction(line, col, cache.reuse(target), data); + } + + @Override + public ProcessingInstruction processingInstruction(final int line, final int col, final String target) { + return super.processingInstruction(line, col, cache.reuse(target)); + } + + @Override + public EntityRef entityRef(final int line, final int col, final String name) { + return super.entityRef(line, col, cache.reuse(name)); + } + + @Override + public EntityRef entityRef(final int line, final int col, final String name, final String publicID, final String systemID) { + return super.entityRef(line, col, cache.reuse(name), publicID, systemID); + } + + @Override + public EntityRef entityRef(final int line, final int col, final String name, final String systemID) { + return super.entityRef(line, col, cache.reuse(name), systemID); + } + +} diff --git a/core/src/java/org/jdom/StringBin.java b/core/src/java/org/jdom/StringBin.java new file mode 100644 index 0000000..2509daf --- /dev/null +++ b/core/src/java/org/jdom/StringBin.java @@ -0,0 +1,389 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.jdom.internal.ArrayCopy; + + +/** + * This is a mechanism for storing and reusing unique instances of Strings. + * The idea is that in XML the tag names, attribute names, and other String + * content is often repeated a lot. Each repeat is typically done as a new + * String instance. This class makes it possible to substantially reduce memory + * usage by reusing String instances instead of keeping the new ones. + *

+ * This class is not the same as String.intern() because String.intern() uses + * the PermGen memory space (very limited in size), whereas this uses the heap. + *

+ * The primary goal of this class is to be as memory efficient as possible. This + * has the interesting side effect of reducing the amount of time spent in + * garbage-collection cycles. While this does increase the amount of time to + * process a String, it means that subsequent String values can be 'recycled' + * fast, and, ideally, never need to leave the 'eden space' in the memory model + * which in turn means that the duplicate strings do not even hit the GC + * overhead. It is easy to measure that this process takes longer than simply + * keeping the duplicate String values, but it is much harder to measure the + * decreased cost of GC. It would be somewhat fair to say that the memory + * benefit is substantial, and the cost of allocation is offset by the savings + * in garbage collection. This trade-off is dependent on the amount of duplicate + * data you have. In XML where there are lots of repeating patterns of element + * and attribute names this can add up pretty fast. + *

+ * This class is not thread-safe. + * + * @author Rolf Lear + * + */ +final class StringBin { + + // Here are some magic numbers: + + /** Default bucket-size growth factor */ + private static final int GROW = 4; + /** + * How many buckets to start with + *

+ * Actually, this just sets the initial capacity which is used to calculate + * the number of buckets. This implementation will turn the default capacity + * of 1023 in to + */ + private static final int DEFAULTCAP = 1023; + /** How big to let the largest bucket grow before a rehash */ + private static final int MAXBUCKET = 64; + + /** + * The actual buckets. + * There is a really good reason to use a two-dimensional array: a 1 + * dimensional array would require us to do an inordinate amount of shifting + * of the data as we insert new values in to the middle. + * By having 'x' number of buckets, the average shift would be 1/x th of the + * amount, which is much faster. + */ + private String[][] buckets; + private int lengths[]; + + /** The bit mask and bit shift */ + private int mask = 0; + + /** + * Create a default instance of the StringBin with the default capacity. + */ + public StringBin() { + this(DEFAULTCAP); + } + + /** + * Create a StringBin instance with a specified initial capacity. + * @param capacity the capacity to set. + */ + public StringBin(int capacity) { + if (capacity < 0) { + throw new IllegalArgumentException("Can not have a negative capacity"); + } + capacity--; + if (capacity < DEFAULTCAP) { + capacity = DEFAULTCAP; + } + // aim for 'GROW - 1' Strings per bucket... + capacity /= (GROW - 1); + int shift = 0; + while (capacity != 0) { + capacity >>>= 1; + shift++; + } + mask = (1 << shift) - 1; + buckets = new String[mask + 1][]; + lengths = new int[buckets.length]; + } + + /** + * This code effectively does a binary search for a value. + * The order of the data in a bucket is increasing-by-hashcode, and then + * for values with the same hashcode, it is increasing by alphabetical. + * @param hash + * @param value + * @param bucket + * @param length + * @return + */ + private final int locate(final int hash, final String value, final String[] bucket, final int length) { + int left = 0; + int right = length -1; + int mid = 0; + while (left <= right) { + mid = (left + right) >>> 1; + if (bucket[mid].hashCode() > hash) { + right = mid - 1; + } else if (bucket[mid].hashCode() < hash) { + left = mid + 1; + } else { + // have the same hashcode + // do a string-compare. + int cmp = value.compareTo(bucket[mid]); + if (cmp == 0) { + // equals. + return mid; + } else if (cmp < 0) { + // our input value comes before the bucket value, search + // backwards + while (--mid >= left && bucket[mid].hashCode() == hash) { + // we have gone back one, and we still have the same + // hash code... let's compare. + cmp = value.compareTo(bucket[mid]); + if (cmp == 0) { + // equals, found it. + return mid; + } else if (cmp > 0) { + // we were searching backwards because we started at + // the mid point which was after the input value. + // now that we have found a value that comes + // before the input value it means we have gone too + // far... which in turn means the insertion point + // is one place after where we are. + return - (mid + 1) - 1; + } + } + // this must mean that we ran out of data, or ran out of + // values with the same hashcode... + return - (mid + 1) - 1; + } else { + // we have a value that comes before the value with the + // same hash as us. + while (++mid <= right && bucket[mid].hashCode() == hash) { + //the next value exists and has the same hash code. + cmp = value.compareTo(bucket[mid]); + if (cmp == 0) { + // found our value. + return mid; + } else if (cmp < 0) { + // we were searching forwards because we started at + // the mid point which was before the input value. + // now that we have found a value that comes + // after the input value it means we have gone too + // far... which in turn means the insertion point + // is at the point where we are. + return - mid - 1; + } + } + // we have run out of values, or the value we are on has a + // different hashcode. + return - mid - 1; + } + } + } + // nothing had the same hashcode. + return -left - 1; + } + + /** + * Get a String instance that is equal to the input value. This may or may + * not be the same instance as the input value. Null input values will + * reuse() as null. + * @param value The value to check. + * @return a String that is equals() to the input value, or null if the + * input was null + */ + public String reuse(final String value) { + if (value == null) { + return null; + } + final int hash = value.hashCode(); + /* + * we use a special masking routine here. This is important. + * we always do a 16-bit shift, XOR it with the unshifted value, and + * then apply the mask. + * The reason is relatively simple: it makes rehashing much faster + * because you never need to shift values in the rehash, they are always + * just appended. + * Further, there is no real need to get a true random distribution in + * the buckets... it's not important. The String.hashCode() is a good + * enough hash function so we do not lose much by doing it this way. + * + * In detail: + * Normally for a bucketing/hashing system we will try to use as many + * bits as possible to create the bucket hash for the value. In this + * case though, we XOR the high 16 bits with the low 16 bits (and keep + * the high 16 bits unchanged. Lets call this the 'interim result'. + * We then apply our bit mask to that interim result to get the bucket + * id. The important thing here is that the interim result is the same + * no matter how many buckets there are. + * + * If we need to rehash the buckets, the mask will include more bits, + * and that guarantees that the values in any one 'original' bucket + * will be divided in to different buckets, and never merged with + * values from a different original bucket. + * + * Thus, in a rehash, both because of the way we calculate the bucketid + * and also because the values in a bucket are stored in increasing + * hash value order, we never need to insert a value in to the middle + * of the rehashed bucket, we can always add to the end. + */ + final int bucketid = ((hash >>> 16) ^ hash) & mask; + + final int length = lengths[bucketid]; + if (length == 0) { + // start a new bucket + final String v = compact(value); + buckets[bucketid] = new String[GROW]; + buckets[bucketid][0] = v; + lengths[bucketid] = 1; + return v; + } + + // get the existing bucket. + String[] bucket = buckets[bucketid]; + + // note the final value calculated as -val - 1 + final int ip = - locate(hash, value, bucket, length) - 1; + if (ip < 0) { + // this means we have found the value. + return bucket[- ip - 1]; + } + if (length >= MAXBUCKET) { + // need to rehash, so we do, and then add our value + rehash(); + return reuse(value); + } + if (length == bucket.length) { + // there is no space for our value. + bucket = ArrayCopy.copyOf(bucket, length + GROW); + buckets[bucketid] = bucket; + } + System.arraycopy(bucket, ip, bucket, ip + 1, length - ip); + final String v = compact(value); + bucket[ip] = v; + lengths[bucketid]++; + return v; + } + + /** + * Store the existing values in a new and larger set of buckets. + * This reduces the number of values in each bucket, which improves insert + * time. + *

+ * The data is stored in hashCode() order, and then alphabetically for + * those instances where two String values have the same hashCode(). + *

+ * The bucketing hash key is calculated in a specific way that guarantees + * that when we rehash there values in a bucket will be divided between + * a new set of buckets, and no other source bucket will ever add values + * to a bucket that we are dividing our bucket to. + *

+ * The combination of the bucket hash key, and the bucket ordering means + * that during a rehash we never have to insert values in to the middle of + * the bucket. + */ + private void rehash() { + String[][] olddata = buckets; + // magic numbers ... we make 4-times as many buckets. + mask = ((mask + 1) << 2) - 1; + buckets = new String[mask + 1][]; + lengths = new int[buckets.length]; + int hash = 0, bucketid = 0, length = 0; + for (String[] ob : olddata) { + if (ob == null) { + // was an empty bucket. + continue; + } + for (String val : ob) { + if (val == null) { + // there are no more values to rehash in this bucket. + break; + } + hash = val.hashCode(); + bucketid = ((hash >>> 16) ^ hash) & mask; + length = lengths[bucketid]; + if (length == 0) { + buckets[bucketid] = new String[(ob.length + GROW) / GROW]; + buckets[bucketid][0] = val; + } else { + if (buckets[bucketid].length == length) { + buckets[bucketid] = ArrayCopy.copyOf( + buckets[bucketid], lengths[bucketid] + GROW); + } + buckets[bucketid][length] = val; + } + lengths[bucketid]++; + } + } + } + + /** + * Compact a Java String to its smallest char[] backing array. + * Java often reuses the char[] array that backs String classes. If you have + * one String value and substring it, or some other methods, then instead of + * creating a new char[] array it reuses the original one. This can lead to + * small String values being backed by very large arrays. We do not want to + * be caching these large arrays... just the smallest. + * @param input The String to compact + * @return a Compacted version of the String. + */ + private static final String compact(final String input) { + return new String(input.toCharArray()); + } + + /** + * Number of registered Strings + * @return the number of registered String values. + */ + public int size() { + int sum = 0; + for (int l : lengths) { + sum += l; + } + return sum; + } + +} diff --git a/core/src/java/org/jdom/Text.java b/core/src/java/org/jdom/Text.java new file mode 100644 index 0000000..c69814f --- /dev/null +++ b/core/src/java/org/jdom/Text.java @@ -0,0 +1,279 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; + +/** + * An XML character sequence. Provides a modular, parentable method of + * representing text. Text makes no guarantees about the underlying textual + * representation of character data, but does expose that data as a Java String. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Bradley S. Huffman + */ +public class Text extends Content { + + /** + * JDOM2 Serialization. In this case, Text is simple. + */ + private static final long serialVersionUID = 200L; + + static final String EMPTY_STRING = ""; + + /** The actual character content */ + // XXX See http://www.servlets.com/archive/servlet/ReadMsg?msgId=8612 + // from elharo for a description of why Java characters may not suffice + // long term + protected String value; + + /** + * CData type extends Text, and it needs to be able to change the Content + * type of this Content. + * @param ctype The CType to set for this Text-based Content. + */ + protected Text(CType ctype) { + super(ctype); + } + + /** + * This is the protected, no-args constructor standard in all JDOM + * classes. It allows subclassers to get a raw instance with no + * initialization. + */ + protected Text() { + this(CType.Text); + } + + /** + * This constructor creates a new Text node, with the + * supplied string value as it's character content. + * + * @param str the node's character content. + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + */ + public Text(String str) { + this(CType.Text); + setText(str); + } + + /** + * This returns the value of this Text node as a Java + * String. + * + * @return String - character content of this node. + */ + public String getText() { + return value; + } + + /** + * This returns the textual content with all surrounding whitespace + * removed. If only whitespace exists, the empty string is returned. + * + * @return trimmed text content or empty string + */ + public String getTextTrim() { + return Format.trimBoth(getText()); + } + + /** + * This returns the textual content with all surrounding whitespace + * removed and internal whitespace normalized to a single space. If + * only whitespace exists, the empty string is returned. + * + * @return normalized text content or empty string + */ + public String getTextNormalize() { + return normalizeString(getText()); + } + + /** + * This returns a new string with all surrounding whitespace + * removed and internal whitespace normalized to a single space. If + * only whitespace exists, the empty string is returned. + *

+ * Per XML 1.0 Production 3 whitespace includes: #x20, #x9, #xD, #xA + *

+ * + * @param str string to be normalized. + * @return normalized string or empty string + */ + public static String normalizeString(String str) { + if (str == null) + return EMPTY_STRING; + + return Format.compact(str); + } + + /** + * This will set the value of this Text node. + * + * @param str value for node's content. + * @return the object on which the method was invoked + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + */ + public Text setText(String str) { + String reason; + + if (str == null) { + value = EMPTY_STRING; + return this; + } + + if ((reason = Verifier.checkCharacterData(str)) != null) { + throw new IllegalDataException(str, "character content", reason); + } + value = str; + return this; + } + + /** + * This will append character content to whatever content already + * exists within this Text node. + * + * @param str character content to append. + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + */ + public void append(String str) { + String reason; + + if (str == null) { + return; + } + if ((reason = Verifier.checkCharacterData(str)) != null) { + throw new IllegalDataException(str, "character content", reason); + } + + if (str.length() > 0) { + value += str; + } + } + + /** + * This will append the content of another Text node + * to this node. + * + * @param text Text node to append. + */ + public void append(Text text) { + if (text == null) { + return; + } + value += text.getText(); + } + + /** + * Returns the XPath 1.0 string value of this element, which is the + * text itself. + * + * @return the text + */ + @Override + public String getValue() { + return value; + } + + /** + * This returns a String representation of the + * Text node, suitable for debugging. If the XML + * representation of the Text node is desired, + * either {@link #getText} or + * {@link XMLOutputter2#outputString(Text)} + * should be used. + * + * @return String - information about this node. + */ + @Override + public String toString() { + return new StringBuilder(64) + .append("[Text: ") + .append(getText()) + .append("]") + .toString(); + } + + @Override + public Text clone() { + Text text = (Text)super.clone(); + text.value = value; + return text; + } + + @Override + public Text detach() { + return (Text)super.detach(); + } + + @Override + protected Text setParent(Parent parent) { + return (Text)super.setParent(parent); + } + + @Override + public Element getParent() { + // because DocType can only be attached to a Document. + return (Element)super.getParent(); + } + +} diff --git a/core/src/java/org/jdom/UncheckedJDOMFactory.java b/core/src/java/org/jdom/UncheckedJDOMFactory.java new file mode 100644 index 0000000..3b93c77 --- /dev/null +++ b/core/src/java/org/jdom/UncheckedJDOMFactory.java @@ -0,0 +1,339 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +/** + * Special factory for building documents without any content or structure + * checking. This should only be used when you are 100% positive that the + * input is absolutely correct. This factory can speed builds, but any + * problems in the input will be uncaught until later when they could cause + * infinite loops, malformed XML, or worse. Use with extreme caution. + * + * @author Various Authors - history is not complete + */ +public class UncheckedJDOMFactory extends DefaultJDOMFactory { + + // ===================================================================== + // Element Factory + // ===================================================================== + + @Override + public Element element(final int line, final int col, final String name, Namespace namespace) { + Element e = new Element(); + e.name = name; + if (namespace == null) { + namespace = Namespace.NO_NAMESPACE; + } + e.namespace = namespace; + return e; + } + + @Override + public Element element(final int line, final int col, final String name) { + Element e = new Element(); + e.name = name; + e.namespace = Namespace.NO_NAMESPACE; + return e; + } + + @Override + public Element element(final int line, final int col, final String name, String uri) { + return element(name, Namespace.getNamespace("", uri)); + } + + @Override + public Element element(final int line, final int col, final String name, String prefix, String uri) { + return element(name, Namespace.getNamespace(prefix, uri)); + } + + // ===================================================================== + // Attribute Factory + // ===================================================================== + + @Override + public Attribute attribute(String name, String value, Namespace namespace) { + Attribute a = new Attribute(); + a.name = name; + a.value = value; + if (namespace == null) { + namespace = Namespace.NO_NAMESPACE; + } + a.namespace = namespace; + return a; + } + + @Override + @Deprecated + public Attribute attribute(String name, String value, int type, Namespace namespace) { + return attribute(name, value, AttributeType.byIndex(type), namespace); + } + + @Override + public Attribute attribute(String name, String value, AttributeType type, Namespace namespace) { + Attribute a = new Attribute(); + a.name = name; + a.type = type; + a.value = value; + if (namespace == null) { + namespace = Namespace.NO_NAMESPACE; + } + a.namespace = namespace; + return a; + } + + @Override + public Attribute attribute(String name, String value) { + Attribute a = new Attribute(); + a.name = name; + a.value = value; + a.namespace = Namespace.NO_NAMESPACE; + return a; + } + + + @Override + @Deprecated + public Attribute attribute(String name, String value, int type) { + return attribute(name, value, AttributeType.byIndex(type)); + } + + @Override + public Attribute attribute(String name, String value, AttributeType type) { + Attribute a = new Attribute(); + a.name = name; + a.type = type; + a.value = value; + a.namespace = Namespace.NO_NAMESPACE; + return a; + } + + // ===================================================================== + // Text Factory + // ===================================================================== + + @Override + public Text text(final int line, final int col, final String str) { + Text t = new Text(); + t.value = str; + return t; + } + + // ===================================================================== + // CDATA Factory + // ===================================================================== + + @Override + public CDATA cdata(final int line, final int col, final String str) { + CDATA c = new CDATA(); + c.value = str; + return c; + } + + // ===================================================================== + // Comment Factory + // ===================================================================== + + @Override + public Comment comment(final int line, final int col, final String str) { + Comment c = new Comment(); + c.text = str; + return c; + } + + // ===================================================================== + // Processing Instruction Factory + // ===================================================================== + + @Override + public ProcessingInstruction processingInstruction(final int line, final int col, final String target, Map data) { + ProcessingInstruction p = new ProcessingInstruction(); + p.target = target; + p.setData(data); + return p; + } + + @Override + public ProcessingInstruction processingInstruction(final int line, final int col, final String target, String data) { + ProcessingInstruction p = new ProcessingInstruction(); + p.target = target; + p.setData(data); + return p; + } + + @Override + public ProcessingInstruction processingInstruction(final int line, final int col, final String target) { + ProcessingInstruction p = new ProcessingInstruction(); + p.target = target; + p.rawData = ""; + return p; + } + + // ===================================================================== + // Entity Ref Factory + // ===================================================================== + + @Override + public EntityRef entityRef(final int line, final int col, final String name) { + EntityRef e = new org.jdom.EntityRef(); + e.name = name; + return e; + } + + @Override + public EntityRef entityRef(final int line, final int col, final String name, String systemID) { + EntityRef e = new EntityRef(); + e.name = name; + e.systemID = systemID; + return e; + } + + @Override + public EntityRef entityRef(final int line, final int col, final String name, String publicID, String systemID) { + EntityRef e = new EntityRef(); + e.name = name; + e.publicID = publicID; + e.systemID = systemID; + return e; + } + + // ===================================================================== + // DocType Factory + // ===================================================================== + + @Override + public DocType docType(final int line, final int col, final String elementName, String publicID, String systemID) { + DocType d = new DocType(); + d.elementName = elementName; + d.publicID = publicID; + d.systemID = systemID; + return d; + } + + @Override + public DocType docType(final int line, final int col, final String elementName, String systemID) { + return docType(elementName, null, systemID); + } + + @Override + public DocType docType(final int line, final int col, final String elementName) { + return docType(elementName, null, null); + } + + // ===================================================================== + // Document Factory + // ===================================================================== + + @Override + public Document document(Element rootElement, DocType docType, String baseURI) { + Document d = new Document(); + if (docType != null) { + addContent(d, docType); + } + if (rootElement != null) { + addContent(d, rootElement); + } + if (baseURI != null) { + d.baseURI = baseURI; + } + return d; + } + + @Override + public Document document(Element rootElement, DocType docType) { + return document(rootElement, docType, null); + } + + @Override + public Document document(Element rootElement) { + return document(rootElement, null, null); + } + + // ===================================================================== + // List manipulation + // ===================================================================== + + @Override + public void addContent(Parent parent, Content child) { + if (parent instanceof Element) { + Element elt = (Element) parent; + elt.content.uncheckedAddContent(child); + } + else { + Document doc = (Document) parent; + doc.content.uncheckedAddContent(child); + } + } + + @Override + public void setAttribute(Element parent, Attribute a) { + parent.getAttributeList().uncheckedAddAttribute(a); + } + + @Override + public void addNamespaceDeclaration(Element parent, Namespace additional) { + if (parent.additionalNamespaces == null) { + parent.additionalNamespaces = new ArrayList(5); //Element.INITIAL_ARRAY_SIZE + } + parent.additionalNamespaces.add(additional); + } + + @Override + public void setRoot(Document doc, Element root) { + doc.content.uncheckedAddContent(root); + } + +} diff --git a/core/src/java/org/jdom/Verifier.java b/core/src/java/org/jdom/Verifier.java new file mode 100644 index 0000000..bb0f338 --- /dev/null +++ b/core/src/java/org/jdom/Verifier.java @@ -0,0 +1,1293 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom; + +import java.util.*; + +/** + * A utility class to handle well-formedness checks on names, data, and other + * verification tasks for JDOM. The class is final and may not be subclassed. + * + * @author Brett McLaughlin + * @author Elliotte Rusty Harold + * @author Jason Hunter + * @author Bradley S. Huffman + * @author Rolf Lear + * @author Wilfried Middleton + */ +final public class Verifier { + + /* + * KEY TO UNDERSTANDING MASKS. + * =========================== + * + * This Verifier uses bitwise logic to perform fast validation on + * XML characters. The concept is as follows... + * + * There are 7 major tests for characters in JDOM and one special case. + * Can the character be a regular character, can it be part of an XML Name + * (element, attribute, entity-ref, etc.), does it represent a letter, + * digit, or combining character. Finally can a character be the first + * character in a name, or can the character be part of a URI. The special + * case is that Attributes and Element names in JDOM do not include the + * namespace prefix, thus, for Attribute and Elements, the name is the + * identical test to other XML names, but excludes the ':'. For performance + * reasons we only have the bitmask for the JDOM names, and then add the + * ':' for the general case tests. + * + * These 7 tests are often performed in very tight performance critical + * loops. It is essential for them to be fast. + * + * These 7 tests conveniently can be represented as 8 bits in a byte. + * We can thus have a single byte that represents the possible roles for + * each possible character. There are 64K characters... thus we need 64K + * bytes to represent each character's possible roles. + * + * We could use arrays of booleans to accomplish the same thing, but each + * boolean is a byte of memory, and using a bitmask allows us to put the + * 8 bitmask tests in the same memory space as just one boolean array. + * + * The end solution is to have an array of these bytes, one per character, + * and to then query each bit on the byte to see whether the corresponding + * character is able to perform in the respective role. + * + * The complicated part of this process is three-fold. The hardest part is + * knowing what role each character can play. The next hard part is + * converting this knowledge in to an array of bytes we can express in this + * Verifier class. The final part is querying that array for each test. + * + * Before this particular performance upgrade, the knowledge of what roles + * each character can play was embedded in each of the isXML*() methods. + * Those methods have been transferred in to the 'contrib' class + * org.jdom.contrib.verifier.VerifierBuilder. That VerifierBuilder class + * has a main method which takes that knowledge, and converts it in to a + * 'compressed' set of two arrays, the byte mask, and the number of + * consecutive characters that have that mask, which are then copy/pasted + * in to this file as the VALCONST and LENCONST arrays. + * + * These two arrays are then 'decompressed' in to the CHARFLAGS array. + * + * The CHARFLAGS array is then queried for each of the 8 critical tests + * to determine which roles a character performs. + * + * If you need to change the roles a character plays in XML (i.e. change + * the return-value of one of the isXML...() methods, then you need to: + * + * - update the logic in org.jdom.contrib.verifier.VerifierBuilder + * - run the VerifierBuilder + * - copy/paste the output to this file. + * - update the JUnit test harness TestVerifier + */ + + /** + * The seed array used with LENCONST to populate CHARFLAGS. + */ + private static final byte[] VALCONST = new byte[] { + 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x41, 0x01, + 0x41, 0x49, 0x41, 0x59, 0x41, 0x01, 0x41, 0x01, + 0x41, 0x4f, 0x01, 0x4d, 0x01, 0x4f, 0x01, 0x41, + 0x01, 0x09, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x09, 0x01, 0x29, 0x01, 0x29, + 0x01, 0x0f, 0x09, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x29, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, + 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x09, 0x0f, 0x29, + 0x01, 0x19, 0x01, 0x29, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x29, 0x0f, 0x29, + 0x01, 0x29, 0x01, 0x19, 0x01, 0x29, 0x01, 0x0f, + 0x01, 0x29, 0x0f, 0x29, 0x01, 0x29, 0x01, 0x0f, + 0x29, 0x01, 0x19, 0x01, 0x29, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x29, 0x01, 0x0f, 0x01, 0x0f, 0x29, + 0x01, 0x19, 0x0f, 0x01, 0x29, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x29, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x19, 0x29, 0x0f, 0x01, 0x29, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x29, 0x0f, 0x29, 0x01, + 0x29, 0x01, 0x29, 0x01, 0x0f, 0x01, 0x19, 0x01, + 0x29, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x29, 0x0f, + 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x19, 0x01, 0x29, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x29, 0x01, 0x19, 0x01, 0x29, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x0f, 0x01, 0x19, 0x01, 0x29, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x19, 0x01, + 0x29, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x0f, 0x01, 0x19, 0x01, 0x0f, 0x01, + 0x0f, 0x29, 0x0f, 0x29, 0x01, 0x0f, 0x09, 0x29, + 0x01, 0x19, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x0f, 0x29, 0x0f, 0x29, 0x01, + 0x29, 0x0f, 0x01, 0x0f, 0x01, 0x09, 0x01, 0x29, + 0x01, 0x19, 0x01, 0x29, 0x01, 0x19, 0x01, 0x29, + 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x0f, 0x01, + 0x0f, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, 0x29, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x29, 0x01, + 0x29, 0x01, 0x0f, 0x01, 0x0f, 0x01, 0x0f, 0x01, + 0x0f, 0x01, 0x09, 0x01, 0x0f, 0x01, 0x0f, 0x29, + 0x01, 0x09, 0x01, 0x0f, 0x01, 0x29, 0x01, 0x09, + 0x01, 0x0f, 0x01, 0x09, 0x01, 0x0f, 0x01, 0x0f, + 0x01, 0x0f, 0x01, 0x00, 0x01, 0x00}; + + /** + * The seed array used with VALCONST to populate CHARFLAGS. + */ + private static final int [] LENCONST = new int [] { + 9, 2, 2, 1, 18, 1, 1, 2, + 9, 2, 1, 10, 1, 2, 1, 1, + 2, 26, 4, 1, 1, 26, 3, 1, + 56, 1, 8, 23, 1, 31, 1, 58, + 2, 11, 2, 8, 1, 53, 1, 68, + 9, 36, 3, 2, 4, 30, 56, 89, + 18, 7, 14, 2, 46, 70, 26, 2, + 36, 1, 1, 3, 1, 1, 1, 20, + 1, 44, 1, 7, 3, 1, 1, 1, + 1, 1, 1, 1, 1, 18, 13, 12, + 1, 66, 1, 12, 1, 36, 1, 4, + 9, 53, 2, 2, 2, 2, 3, 28, + 2, 8, 2, 2, 55, 38, 2, 1, + 7, 38, 10, 17, 1, 23, 1, 3, + 1, 1, 1, 2, 1, 1, 11, 27, + 5, 3, 46, 26, 5, 1, 10, 8, + 13, 10, 6, 1, 71, 2, 5, 1, + 15, 1, 4, 1, 1, 15, 2, 2, + 1, 4, 2, 10, 519, 3, 1, 53, + 2, 1, 1, 16, 3, 4, 3, 10, + 2, 2, 10, 17, 3, 1, 8, 2, + 2, 2, 22, 1, 7, 1, 1, 3, + 4, 2, 1, 1, 7, 2, 2, 2, + 3, 9, 1, 4, 2, 1, 3, 2, + 2, 10, 2, 16, 1, 2, 6, 4, + 2, 2, 22, 1, 7, 1, 2, 1, + 2, 1, 2, 2, 1, 1, 5, 4, + 2, 2, 3, 11, 4, 1, 1, 7, + 10, 2, 3, 12, 3, 1, 7, 1, + 1, 1, 3, 1, 22, 1, 7, 1, + 2, 1, 5, 2, 1, 1, 8, 1, + 3, 1, 3, 18, 1, 5, 10, 17, + 3, 1, 8, 2, 2, 2, 22, 1, + 7, 1, 2, 2, 4, 2, 1, 1, + 6, 3, 2, 2, 3, 8, 2, 4, + 2, 1, 3, 4, 10, 18, 2, 1, + 6, 3, 3, 1, 4, 3, 2, 1, + 1, 1, 2, 3, 2, 3, 3, 3, + 8, 1, 3, 4, 5, 3, 3, 1, + 4, 9, 1, 15, 9, 17, 3, 1, + 8, 1, 3, 1, 23, 1, 10, 1, + 5, 4, 7, 1, 3, 1, 4, 7, + 2, 9, 2, 4, 10, 18, 2, 1, + 8, 1, 3, 1, 23, 1, 10, 1, + 5, 4, 7, 1, 3, 1, 4, 7, + 2, 7, 1, 1, 2, 4, 10, 18, + 2, 1, 8, 1, 3, 1, 23, 1, + 16, 4, 6, 2, 3, 1, 4, 9, + 1, 8, 2, 4, 10, 145, 46, 1, + 1, 1, 2, 7, 5, 6, 1, 8, + 1, 10, 39, 2, 1, 1, 2, 2, + 1, 1, 2, 1, 6, 4, 1, 7, + 1, 3, 1, 1, 1, 1, 2, 2, + 1, 2, 1, 1, 1, 2, 6, 1, + 2, 1, 2, 5, 1, 1, 1, 6, + 2, 10, 62, 2, 6, 10, 11, 1, + 1, 1, 1, 1, 4, 2, 8, 1, + 33, 7, 20, 1, 6, 4, 6, 1, + 1, 1, 21, 3, 7, 1, 1, 230, + 38, 10, 39, 9, 1, 1, 2, 1, + 3, 1, 1, 1, 2, 1, 5, 41, + 1, 1, 1, 1, 1, 11, 1, 1, + 1, 1, 1, 3, 2, 3, 1, 5, + 3, 1, 1, 1, 1, 1, 1, 1, + 1, 3, 2, 3, 2, 1, 1, 40, + 1, 9, 1, 2, 1, 2, 2, 7, + 2, 1, 1, 1, 7, 40, 1, 4, + 1, 8, 1, 3078, 156, 4, 90, 6, + 22, 2, 6, 2, 38, 2, 6, 2, + 8, 1, 1, 1, 1, 1, 1, 1, + 31, 2, 53, 1, 7, 1, 1, 3, + 3, 1, 7, 3, 4, 2, 6, 4, + 13, 5, 3, 1, 7, 211, 13, 4, + 1, 68, 1, 3, 2, 2, 1, 81, + 3, 3714, 1, 1, 1, 25, 9, 6, + 1, 5, 11, 84, 4, 2, 2, 2, + 2, 90, 1, 3, 6, 40, 7379, 20902, + 3162, 11172, 92, 2048, 8190, 2}; + + /** + * The number of characters in Java. + */ + private static final int CHARCNT = Character.MAX_VALUE + 1; + + /** + * An array of byte where each byte represents the roles that the + * corresponding character can play. Use the bit mask values + * to access each character's role. + */ + private static final byte[] CHARFLAGS = buildBitFlags(); + + /** + * Convert the two compressed arrays in to th CHARFLAGS array. + * @return the CHARFLAGS array. + */ + private static final byte[] buildBitFlags() { + final byte[] ret = new byte[CHARCNT]; + int index = 0; + for (int i = 0; i < VALCONST.length; i++) { + // v represents the roles a character can play. + final byte v = VALCONST[i]; + // l is the number of consecutive chars that have the same + // roles 'v' + int l = LENCONST[i]; + // we need to give the next 'l' chars the role bits 'v' + while (--l >= 0) { + ret[index++] = v; + } + } + return ret; + } + + /** Mask used to test for {@link #isXMLCharacter(int)} */ + private static final byte MASKXMLCHARACTER = 1 << 0; + /** Mask used to test for {@link #isXMLLetter(char)} */ + private static final byte MASKXMLLETTER = 1 << 1; + /** Mask used to test for {@link #isXMLNameStartCharacter(char)} */ + private static final byte MASKXMLSTARTCHAR = 1 << 2; + /** Mask used to test for {@link #isXMLNameCharacter(char)} */ + private static final byte MASKXMLNAMECHAR = 1 << 3; + /** Mask used to test for {@link #isXMLDigit(char)} */ + private static final byte MASKXMLDIGIT = 1 << 4; + /** Mask used to test for {@link #isXMLCombiningChar(char)} */ + private static final byte MASKXMLCOMBINING = 1 << 5; + /** Mask used to test for {@link #isURICharacter(char)} */ + private static final byte MASKURICHAR = 1 << 6; + /** Mask used to test for {@link #isXMLLetterOrDigit(char)} */ + private static final byte MASKXMLLETTERORDIGIT = MASKXMLLETTER | MASKXMLDIGIT; + + /** + * Ensure instantation cannot occur. + */ + private Verifier() { } + + private static final String checkJDOMName(final String name) { + // Check basic XML name rules first + // Cannot be empty or null + if (name == null) { + return "XML names cannot be null"; + } + + //final int len = name.length(); + if (name.length() == 0) { + return "XML names cannot be empty"; + } + + // Cannot start with a number + if ((byte)0 == (CHARFLAGS[name.charAt(0)] & MASKXMLSTARTCHAR)) { + return "XML name '" + name + "' cannot begin with the character \"" + + name.charAt(0) + "\""; + } + // Ensure legal content for non-first chars + // also check char 0 to catch colon char ':' + for (int i = name.length() - 1; i >= 1; i--) { + if ((byte)0 == (byte)(CHARFLAGS[name.charAt(i)] & MASKXMLNAMECHAR)) { + return "XML name '" + name + "' cannot contain the character \"" + + name.charAt(i) + "\""; + } + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Element} name. + * + * @param name String name to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkElementName(final String name) { + return checkJDOMName(name); + } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Attribute} name. + * + * @param name String name to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkAttributeName(final String name) { + // Attribute names may not be xmlns since we do this internally too + if ("xmlns".equals(name)) { + return "An Attribute name may not be \"xmlns\"; " + + "use the Namespace class to manage namespaces"; + } + + return checkJDOMName(name); + } + + /** + * This will check the supplied string to see if it only contains + * characters allowed by the XML 1.0 specification. The C0 controls + * (e.g. null, vertical tab, form-feed, etc.) are specifically excluded + * except for carriage return, line-feed, and the horizontal tab. + * Surrogates are also excluded. + *

+ * This method is useful for checking element content and attribute + * values. Note that characters + * like " and < are allowed in attribute values and element content. + * They will simply be escaped as " or < + * when the value is serialized. + *

+ * + * @param text String value to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkCharacterData(final String text) { + if (text == null) { + return "A null is not a legal XML value"; + } + + final int len = text.length(); + for (int i = 0; i < len; i++) { + // we are expecting a normal char, but may be a surrogate. + // the isXMLCharacter method takes an int argument, but we have a char. + // we save a lot of time by doing the test directly here without + // doing the unnecessary cast-to-int and double-checking ranges + // for the char. + // Also, note that we only need to check for non-zero flags, instead + // of checking for an actual bit, because all the other + // character roles are a pure subset of CharacterData. Put another way, + // any character with any bit set, will always also have the + // CharacterData bit set. + while (CHARFLAGS[text.charAt(i)] != (byte)0) { + // fast-loop through the chars until we find something that's not. + if (++i == len) { + // we passed all the characters... + return null; + } + } + // the character is not a normal character. + // we need to sort out what it is. Neither high nor low + // surrogate pairs are valid characters, so they will get here. + + if (isHighSurrogate(text.charAt(i))) { + // we have the valid high char of a pair. + // we will expect the low char on the next index, + i++; + if (i >= len) { + return String.format("Truncated Surrogate Pair 0x%04x????", + (int)text.charAt(i - 1)); + } + if (isLowSurrogate(text.charAt(i))) { + // we now have the low char of a pair, decode and validate + if (!isXMLCharacter(decodeSurrogatePair( + text.charAt(i - 1), text.charAt(i)))) { + // Likely this character can't be easily displayed + // because it's a control so we use it'd hexadecimal + // representation in the reason. + return String.format("0x%06x is not a legal XML character", + decodeSurrogatePair( + text.charAt(i - 1), text.charAt(i))); + } + } else { + // we got a normal character, but we wanted a low surrogate + return String.format("Illegal Surrogate Pair 0x%04x%04x", + (int)text.charAt(i - 1), (int)text.charAt(i)); + } + } else { + // Likely this character can't be easily displayed + // because it's a control so we use its hexadecimal + // representation in the reason. + return String.format("0x%04x is not a legal XML character", + (int)text.charAt(i)); + } + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied data to see if it is legal for use as + * JDOM {@link CDATA}. + * + * @param data String data to check. + * @return String reason data is illegal, or + * null is name is OK. + */ + public static String checkCDATASection(final String data) { + String reason = null; + if ((reason = checkCharacterData(data)) != null) { + return reason; + } + + if (data.indexOf("]]>") != -1) { + return "CDATA cannot internally contain a CDATA ending " + + "delimiter (]]>)"; + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Namespace} prefix. + * + * @param prefix String prefix to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkNamespacePrefix(final String prefix) { + // Manually do rules, since URIs can be null or empty + if ((prefix == null) || (prefix.equals(""))) { + return null; + } + + if (checkJDOMName(prefix) != null) { + // will double-check null and empty names, but that's OK + // since we have already checked them. + return checkJDOMName(prefix); + } + + // Cannot start with "xml" in any character case + /* See Issue 126 - https://github.com/hunterhacker/jdom/issues/126 + if (prefix.length() >= 3) { + if (prefix.charAt(0) == 'x' || prefix.charAt(0) == 'X') { + if (prefix.charAt(1) == 'm' || prefix.charAt(1) == 'M') { + if (prefix.charAt(2) == 'l' || prefix.charAt(2) == 'L') { + return "Namespace prefixes cannot begin with " + + "\"xml\" in any combination of case"; + } + } + } + } + */ + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied name to see if it is legal for use as + * a JDOM {@link Namespace} URI. + *

+ * This is a 'light' test of URI's designed to filter out only the worst + * illegal URIs. It tests only to ensure the first character is valid. A + * comprehensive URI validation process would be impractical. + * + * @param uri String URI to check. + * @return String reason name is illegal, or + * null if name is OK. + */ + public static String checkNamespaceURI(final String uri) { + // Manually do rules, since URIs can be null or empty + if ((uri == null) || (uri.equals(""))) { + return null; + } + + // Cannot start with a number + final char first = uri.charAt(0); + if (Character.isDigit(first)) { + return "Namespace URIs cannot begin with a number"; + } + // Cannot start with a $ + if (first == '$') { + return "Namespace URIs cannot begin with a dollar sign ($)"; + } + // Cannot start with a - + if (first == '-') { + return "Namespace URIs cannot begin with a hyphen (-)"; + } + + // Cannot start with space... + if (isXMLWhitespace(first)) { + return "Namespace URIs cannot begin with white-space"; + } + + // If we got here, everything is OK + return null; + } + + /** + * Check if two namespaces collide. + * + * @param namespace Namespace to check. + * @param other Namespace to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Namespace other) { + String p1,p2,u1,u2,reason; + + reason = null; + p1 = namespace.getPrefix(); + u1 = namespace.getURI(); + p2 = other.getPrefix(); + u2 = other.getURI(); + if (p1.equals(p2) && !u1.equals(u2)) { + reason = "The namespace prefix \"" + p1 + "\" collides"; + } + return reason; + } + + /** + * Check if {@link Attribute}'s namespace collides with a + * {@link Element}'s namespace. + * + * @param attribute Attribute to check. + * @param element Element to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Attribute attribute, + final Element element) { + return checkNamespaceCollision(attribute, element, -1); + } + + /** + * Check if {@link Attribute}'s namespace collides with a + * {@link Element}'s namespace. + * + * @param attribute Attribute to check. + * @param element Element to check against. + * @param ignoreatt Ignore a specific Attribute (if it exists) when + * calculating any collisions (used when replacing one attribute + * with another). + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Attribute attribute, + final Element element, final int ignoreatt) { + final Namespace namespace = attribute.getNamespace(); + final String prefix = namespace.getPrefix(); + if ("".equals(prefix)) { + return null; + } + + return checkNamespaceCollision(namespace, element, ignoreatt); + } + + /** + * Check if a {@link Namespace} collides with a + * {@link Element}'s namespace. + * + * @param namespace Namespace to check. + * @param element Element to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Element element) { + return checkNamespaceCollision(namespace, element, -1); + } + + /** + * Check if a {@link Namespace} collides with a + * {@link Element}'s namespace. + * + * @param namespace Namespace to check. + * @param element Element to check against. + * @param ignoreatt Ignore a specific Attribute (if it exists) when + * calculating any collisions (used when replacing one attribute + * with another). + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Element element, final int ignoreatt) { + String reason = checkNamespaceCollision(namespace, + element.getNamespace()); + if (reason != null) { + return reason + " with the element namespace prefix"; + } + + if (element.hasAdditionalNamespaces()) { + reason = checkNamespaceCollision(namespace, + element.getAdditionalNamespaces()); + if (reason != null) { + return reason; + } + } + + if (element.hasAttributes()) { + reason = checkNamespaceCollision(namespace, element.getAttributes(), ignoreatt); + if (reason != null) { + return reason; + } + } + + return null; + } + + /** + * Check if a {@link Namespace} collides with a + * {@link Attribute}'s namespace. + * + * @param namespace Namespace to check. + * @param attribute Attribute to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final Attribute attribute) { + String reason = null; + if (!attribute.getNamespace().equals(Namespace.NO_NAMESPACE)) { + reason = checkNamespaceCollision(namespace, + attribute.getNamespace()); + if (reason != null) { + reason += " with an attribute namespace prefix on the element"; + } + } + return reason; + } + + /** + * Check if a {@link Namespace} collides with any namespace + * from a list of objects. + * + * @param namespace Namespace to check. + * @param list List to check against. + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final List list) { + return checkNamespaceCollision(namespace, list, -1); + } + + /** + * Check if a {@link Namespace} collides with any namespace + * from a list of objects. + * + * @param namespace Namespace to check. + * @param list List to check against. + * @param ignoreatt Ignore a specific Attribute (if it exists) when + * calculating any collisions (used when replacing one attribute + * with another). + * @return String reason for collision, or + * null if no collision. + */ + public static String checkNamespaceCollision(final Namespace namespace, + final List list, final int ignoreatt) { + if (list == null) { + return null; + } + + String reason = null; + final Iterator i = list.iterator(); + int cnt = -1; + while ((reason == null) && i.hasNext()) { + final Object obj = i.next(); + cnt++; + if (obj instanceof Attribute) { + if (cnt == ignoreatt) { + continue; + } + reason = checkNamespaceCollision(namespace, (Attribute) obj); + } + else if (obj instanceof Element) { + reason = checkNamespaceCollision(namespace, (Element) obj); + } + else if (obj instanceof Namespace) { + reason = checkNamespaceCollision(namespace, (Namespace) obj); + if (reason != null) { + reason += " with an additional namespace declared" + + " by the element"; + } + } + } + return reason; + } + + /** + * This will check the supplied data to see if it is legal for use as + * a JDOM {@link ProcessingInstruction} target. + * + * @param target String target to check. + * @return String reason target is illegal, or + * null if target is OK. + */ + public static String checkProcessingInstructionTarget(final String target) { + // Check basic XML name rules first + String reason; + if ((reason = checkXMLName(target)) != null) { + return reason; + } + + // No colons allowed, per Namespace Specification Section 6 + if (target.indexOf(":") != -1) { + return "Processing instruction targets cannot contain colons"; + } + + // Cannot begin with 'xml' in any case + if (target.equalsIgnoreCase("xml")) { + return "Processing instructions cannot have a target of " + + "\"xml\" in any combination of case. (Note that the " + + "\"\" declaration at the beginning of a " + + "document is not a processing instruction and should not " + + "be added as one; it is written automatically during " + + "output, e.g. by XMLOutputter.)"; + } + + // If we got here, everything is OK + return null; + } + + /** + * This will check the supplied data to see if it is legal for use as + * {@link ProcessingInstruction} data. Besides checking that + * all the characters are allowed in XML, this also checks + * that the data does not contain the PI end-string "?>". + * + * @param data String data to check. + * @return String reason data is illegal, or + * null if data is OK. + */ + public static String checkProcessingInstructionData(final String data) { + // Check basic XML name rules first + final String reason = checkCharacterData(data); + + if (reason == null) { + if (data.indexOf("?>") >= 0) { + return "Processing instructions cannot contain " + + "the string \"?>\""; + } + } + + return reason; + } + + /** + * This will check the supplied data to see if it is legal for use as + * JDOM {@link Comment} data. + * + * @param data String data to check. + * @return String reason data is illegal, or + * null if data is OK. + */ + public static String checkCommentData(final String data) { + String reason = null; + if ((reason = checkCharacterData(data)) != null) { + return reason; + } + + if (data.indexOf("--") != -1) { + return "Comments cannot contain double hyphens (--)"; + } + if (data.endsWith("-")) { + return "Comment data cannot end with a hyphen."; + } + + // If we got here, everything is OK + return null; + } + /** + * This is a utility function to decode a non-BMP + * UTF-16 surrogate pair. + * @param high high 16 bits + * @param low low 16 bits + * @return decoded character + */ + public static int decodeSurrogatePair(final char high, final char low) { + return 0x10000 + (high - 0xD800) * 0x400 + (low - 0xDC00); + } + + /** + * This will check the supplied data to see if it is legal for use as + * PublicID (in a {@link DocType} or {@link EntityRef}). + * + * @param c the character to validate + * @return String reason c is illegal, or + * null if c is OK. + */ + public static boolean isXMLPublicIDCharacter(final char c) { + // [13] PubidChar ::= #x20 | #xD | #xA | [a-zA-Z0-9] | + // [-'()+,./:=?;*#@$_%] + + if (c >= 'a' && c <= 'z') return true; + if (c >= '?' && c <= 'Z') return true; + if (c >= '\'' && c <= ';') return true; + + if (c == ' ') return true; + if (c == '!') return true; + if (c == '=') return true; + if (c == '#') return true; + if (c == '$') return true; + if (c == '_') return true; + if (c == '%') return true; + if (c == '\n') return true; + if (c == '\r') return true; + if (c == '\t') return true; + + return false; + } + + /** + * This will ensure that the data for a public identifier + * is legal. + * + * @param publicID String public ID to check. + * @return String reason public ID is illegal, or + * null if public ID is OK. + */ + public static String checkPublicID(final String publicID) { + String reason = null; + + if (publicID == null) return null; + // This indicates there is no public ID + + for (int i = 0; i < publicID.length(); i++) { + final char c = publicID.charAt(i); + if (!isXMLPublicIDCharacter(c)) { + reason = c + " is not a legal character in public IDs"; + break; + } + } + + return reason; + } + + + /** + * This will ensure that the data for a system literal + * is legal. + * + * @param systemLiteral String system literal to check. + * @return String reason system literal is illegal, or + * null if system literal is OK. + */ + public static String checkSystemLiteral(final String systemLiteral) { + String reason = null; + + if (systemLiteral == null) return null; + // This indicates there is no system ID + + if (systemLiteral.indexOf('\'') != -1 + && systemLiteral.indexOf('"') != -1) { + reason = + "System literals cannot simultaneously contain both single and double quotes."; + } + else { + reason = checkCharacterData(systemLiteral); + } + + return reason; + } + + /** + * This is a utility function for sharing the base process of checking + * any XML name. + * + * @param name String to check for XML name compliance. + * @return String reason the name is illegal, or + * null if OK. + */ + public static String checkXMLName(final String name) { + // Cannot be empty or null + if ((name == null)) { + return "XML names cannot be null"; + } + + final int len = name.length(); + if (len == 0) { + return "XML names cannot be empty"; + } + + + // Cannot start with a number + if (!isXMLNameStartCharacter(name.charAt(0))) { + return "XML names cannot begin with the character \"" + + name.charAt(0) + "\""; + } + // Ensure legal content for non-first chars + for (int i = 1; i < len; i++) { + if (!isXMLNameCharacter(name.charAt(i))) { + return "XML names cannot contain the character \"" + name.charAt(i) + "\""; + } + } + + // We got here, so everything is OK + return null; + } + + /** + *

+ * Checks a string to see if it is a legal RFC 2396 URI. + * Both absolute and relative URIs are supported. + *

+ * + * @param uri String to check. + * @return String reason the URI is illegal, or + * null if OK. + */ + public static String checkURI(final String uri) { + // URIs can be null or empty + if ((uri == null) || (uri.equals(""))) { + return null; + } + + for (int i = 0; i < uri.length(); i++) { + final char test = uri.charAt(i); + if (!isURICharacter(test)) { + String msgNumber = "0x" + Integer.toHexString(test); + if (test <= 0x09) msgNumber = "0x0" + Integer.toHexString(test); + return "URIs cannot contain " + msgNumber; + } // end if + if (test == '%') { // must be followed by two hexadecimal digits + try { + final char firstDigit = uri.charAt(i+1); + final char secondDigit = uri.charAt(i+2); + if (!isHexDigit(firstDigit) || + !isHexDigit(secondDigit)) { + return "Percent signs in URIs must be followed by " + + "exactly two hexadecimal digits."; + } + + } + catch (final StringIndexOutOfBoundsException e) { + return "Percent signs in URIs must be followed by " + + "exactly two hexadecimal digits."; + } + } + } // end for + + // If we got here, everything is OK + return null; + } + + /** + *

+ * This is a utility function for determining whether a specified + * Unicode character is a hexadecimal digit as defined in RFC 2396; + * that is, one of the ASCII characters 0-9, a-f, or A-F. + *

+ * + * @param c to check for hex digit. + * @return true if it's allowed, false otherwise. + */ + public static boolean isHexDigit(final char c) { + + // I suspect most characters passed to this method will be + // correct hexadecimal digits, so I test for the true cases + // first. If this proves to be a performance bottleneck + // a switch statement or lookup table + // might optimize this. + if (c >= '0' && c <= '9') return true; + if (c >= 'A' && c <= 'F') return true; + if (c >= 'a' && c <= 'f') return true; + + return false; + } + + /** + * This is a function for determining whether the + * specified character is the high 16 bits in a + * UTF-16 surrogate pair. + * @param ch character to check + * @return true if the character is a high surrogate, false otherwise + */ + public static boolean isHighSurrogate(final char ch) { + // faster way to do it is with bit manipulation.... + // return (ch >= 0xD800 && ch <= 0xDBFF); + // A high surrogate has the bit pattern: + // 110110xx xxxxxxxx + // ch & 0xFC00 does a bit-mask of the most significant 6 bits (110110) + // return 0xD800 == (ch & 0xFC00); + // as it happens, it is faster to do a bit-shift, + return 0x36 == ch >>> 10; + } + + /** + * This is a function for determining whether the + * specified character is the low 16 bits in a + * UTF-16 surrogate pair. + * @param ch character to check + * @return true if the character is a low surrogate, false otherwise. + */ + public static boolean isLowSurrogate(final char ch) { + // faster way to do it is with bit manipulation.... + // return (ch >= 0xDC00 && ch <= 0xDFFF); + return 0x37 == ch >>> 10; + } + + /** + *

+ * This is a utility function for determining whether + * a specified Unicode character is legal in URI references + * as determined by RFC 2396. + *

+ * + * @param c char to check for URI reference compliance. + * @return true if it's allowed, false otherwise. + */ + public static boolean isURICharacter(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKURICHAR); + } + + /** + * This is a utility function for determining whether a specified + * character is a character according to production 2 of the + * XML 1.0 specification. + * + * @param c char to check for XML compliance + * @return boolean true if it's a character, + * false otherwise + */ + public static boolean isXMLCharacter(final int c) { + if (c >= CHARCNT) { + return c <= 0x10FFFF; + } + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLCHARACTER); + } + + + /** + * This is a utility function for determining whether a specified + * character is a name character according to production 4 of the + * XML 1.0 specification. + * + * @param c char to check for XML name compliance. + * @return boolean true if it's a name character, + * false otherwise. + */ + public static boolean isXMLNameCharacter(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLNAMECHAR) || c == ':'; + } + + /** + * This is a utility function for determining whether a specified + * character is a legal name start character according to production 5 + * of the XML 1.0 specification. This production does allow names + * to begin with colons which the Namespaces in XML Recommendation + * disallows. + * + * @param c char to check for XML name start compliance. + * @return boolean true if it's a name start character, + * false otherwise. + */ + public static boolean isXMLNameStartCharacter(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLSTARTCHAR) || c == ':'; + } + + /** + * This is a utility function for determining whether a specified + * character is a letter or digit according to productions 84 and 88 + * of the XML 1.0 specification. + * + * @param c char to check. + * @return boolean true if it's letter or digit, + * false otherwise. + */ + public static boolean isXMLLetterOrDigit(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLLETTERORDIGIT); + } + + /** + * This is a utility function for determining whether a specified character + * is a letter according to production 84 of the XML 1.0 specification. + * + * @param c char to check for XML name compliance. + * @return String true if it's a letter, false otherwise. + */ + public static boolean isXMLLetter(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLLETTER); + } + + /** + * This is a utility function for determining whether a specified character + * is a combining character according to production 87 + * of the XML 1.0 specification. + * + * @param c char to check. + * @return boolean true if it's a combining character, + * false otherwise. + */ + public static boolean isXMLCombiningChar(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLCOMBINING); + } + + /** + * This is a utility function for determining whether a specified + * character is an extender according to production 88 of the XML 1.0 + * specification. + * + * @param c char to check. + * @return String true if it's an extender, false otherwise. + */ + public static boolean isXMLExtender(final char c) { + /* + * This function is not accellerated by the bitmask system because + * there are no longer any actual calls to it from the JDOM code. + * It used to be called by the isXMLNameCharacter() method before + * the bitmask optimization. Now the VerifierBuilder code actually + * calls this method instead. + */ + + if (c < 0x00B6) return false; // quick short circuit + + // Extenders + if (c == 0x00B7) return true; + if (c == 0x02D0) return true; + if (c == 0x02D1) return true; + if (c == 0x0387) return true; + if (c == 0x0640) return true; + if (c == 0x0E46) return true; + if (c == 0x0EC6) return true; + if (c == 0x3005) return true; + + if (c < 0x3031) return false; if (c <= 0x3035) return true; + if (c < 0x309D) return false; if (c <= 0x309E) return true; + if (c < 0x30FC) return false; if (c <= 0x30FE) return true; + + return false; + + } + + /** + * This is a utility function for determining whether a specified + * Unicode character + * is a digit according to production 88 of the XML 1.0 specification. + * + * @param c char to check for XML digit compliance + * @return boolean true if it's a digit, false otherwise + */ + public static boolean isXMLDigit(final char c) { + return (byte)0 != (byte)(CHARFLAGS[c] & MASKXMLDIGIT); + } + + /** + * This is a utility function for determining whether a specified + * Unicode character is a whitespace character according to production 3 + * of the XML 1.0 specification. + * + * @param c char to check for XML whitespace compliance + * @return boolean true if it's a whitespace, false otherwise + */ + public static boolean isXMLWhitespace(final char c) { + // the following if is faster than switch statements. + // seems the implicit conversion to int is slower than + // the fall-through or's + if (c==' ' || c=='\n' || c=='\t' || c=='\r' ){ + return true; + } + return false; + } + + /** + * This is a utility function for determining whether a specified + * String is a whitespace character according to production 3 + * of the XML 1.0 specification. + *

+ * This method delegates the individual calls for each character to + * {@link #isXMLWhitespace(char)}. + * + * @param value + * The value to inspect + * @return true if all characters in the input value are all whitespace + * (or the string is the empty-string). + * @since JDOM2 + */ + public static final boolean isAllXMLWhitespace(final String value) { + // Doing the count-down instead of a count-up saves a single int + // variable declaration. + int i = value.length(); + while (--i >= 0) { + if (!isXMLWhitespace(value.charAt(i))) { + return false; + } + } + return true; + } + + +} diff --git a/core/src/java/org/jdom/adapters/AbstractDOMAdapter.java b/core/src/java/org/jdom/adapters/AbstractDOMAdapter.java new file mode 100644 index 0000000..62a3874 --- /dev/null +++ b/core/src/java/org/jdom/adapters/AbstractDOMAdapter.java @@ -0,0 +1,147 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.adapters; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.jdom.DocType; +import org.jdom.JDOMException; +import org.w3c.dom.DOMImplementation; +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; + +/** + * A DOMAdapter utility abstract base class. Uses the concrete implementation + * to build a org.w3c.dom.Document instance, which in turn is used to apply + * the DocType. + *

+ * Special attention should be paid to the setInternalSubset protected method, + * which may, or may not be supported by your actual DOM implementation. + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public abstract class AbstractDOMAdapter implements DOMAdapter { + + /** + * This creates an empty Document object based + * on a specific parser implementation with the given DOCTYPE. + * If the doctype parameter is null, the behavior is the same as + * calling createDocument(). + * + * @param doctype Initial DocType of the document. + * @return Document - created DOM Document. + * @throws JDOMException when errors occur. + */ + @Override + public Document createDocument(DocType doctype) throws JDOMException { + if (doctype == null) { + return createDocument(); + } + + DOMImplementation domImpl = createDocument().getImplementation(); + DocumentType domDocType = domImpl.createDocumentType( + doctype.getElementName(), + doctype.getPublicID(), + doctype.getSystemID()); + + // Set the internal subset if possible + setInternalSubset(domDocType, doctype.getInternalSubset()); + + Document ret = domImpl.createDocument("http://temporary", + doctype.getElementName(), + domDocType); + + Element root = ret.getDocumentElement(); + if (root != null) { + ret.removeChild(root); + } + + return ret; + } + + /** + * This attempts to change the DocumentType to have the given internal DTD + * subset value. This is not a standard ability in DOM, so it's only + * available with some parsers. Subclasses can alter the mechanism by + * which the attempt is made to set the value. + * + * @param dt DocumentType to be altered + * @param s String to use as the internal DTD subset + */ + protected void setInternalSubset(DocumentType dt, String s) { + if (dt == null || s == null) return; + + // Default behavior is to attempt a setInternalSubset() call using + // reflection. This method is not part of the DOM spec, but it's + // available on Xerces 1.4.4+. It's not currently in Crimson. + try { + Class dtclass = dt.getClass(); + Method setInternalSubset = dtclass.getMethod( + "setInternalSubset", String.class); + setInternalSubset.invoke(dt, s); + } catch (InvocationTargetException e) { + // ignore + } catch (IllegalAccessException e) { + // ignore + } catch (SecurityException e) { + // ignore + } catch (NoSuchMethodException e) { + // ignore + } + } +} diff --git a/core/src/java/org/jdom/adapters/DOMAdapter.java b/core/src/java/org/jdom/adapters/DOMAdapter.java new file mode 100644 index 0000000..8e94d75 --- /dev/null +++ b/core/src/java/org/jdom/adapters/DOMAdapter.java @@ -0,0 +1,113 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.adapters; + +import org.jdom.DocType; +import org.jdom.JDOMException; +import org.jdom.output.DOMOutputter; + +import org.w3c.dom.Document; + +/** + * Defines a standard set of adapter methods for interfacing with a DOM parser + * and obtaining a DOM {@link org.w3c.dom.Document org.w3c.dom.Document} object. + * Instances of this interface are used by the {@link DOMOutputter} class to + * create a DOM output result using the org.w3c.dom.Document implementation + * returned by these methods. + *

+ * You should never need to implement this interface unless you have a specific + * need to use something other than the default JAXP-based mechanism. + *

+ * JDOM only provides one 'concrete' implementation of the DOMAdapter: the + * {@link JAXPDOMAdapter} class. That implementation is a thread-safe and + * efficient implementation. It can be used as a template for building your own + * custom DOMAdapter implementation, if you need it. + *

+ * The {@link AbstractDOMAdapter} class could help you by implementing the + * DocType-based method which leverages the base createDocument() method. + *

+ * Special note for implementation of DOMAdapter: For backward + * compatibility with JDOM 1.x (which allows a class-name to be used to specify + * a DOMAdapter in the DOMOoutputter class), it is required that your + * implementations of DOMAdapter have a no-argument default constructor. If you + * require a constructor argument then you have to ensure that you use the + * correct (non-deprecated) mechanisms on DOMOutputter to specify your custom + * DOMAdapter. + * + * + * @author Brett McLaughlin + * @author Jason Hunter + */ +public interface DOMAdapter { + + /** + * This creates an empty Document object based + * on a specific parser implementation. + * + * @return Document - created DOM Document. + * @throws JDOMException if an error occurs. + */ + public Document createDocument() throws JDOMException; + + /** + * This creates an empty Document object based + * on a specific parser implementation with the given DOCTYPE. + * + * @param doctype Initial DocType of the document. + * @return Document - created DOM Document. + * @throws JDOMException if an error occurs. + */ + public Document createDocument(DocType doctype) throws JDOMException; +} diff --git a/core/src/java/org/jdom/adapters/JAXPDOMAdapter.java b/core/src/java/org/jdom/adapters/JAXPDOMAdapter.java new file mode 100644 index 0000000..98c45c1 --- /dev/null +++ b/core/src/java/org/jdom/adapters/JAXPDOMAdapter.java @@ -0,0 +1,107 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.adapters; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; + +import org.jdom.JDOMException; + +/** + * A DOMAdapter that uses JAXP to obtain a org.w3c.dom.Document instance. + *

+ * This class is fully thread-safe. + * + * @author Jason Hunter + * @author Rolf Lear + */ +public class JAXPDOMAdapter extends AbstractDOMAdapter { + + /** + * Use a Thread-local for keeping a single instance of a + * DocumentBuilder in memory. Thread-safe this way. + */ + private static final ThreadLocal localbuilder = + new ThreadLocal(); + + /** + * This creates an empty Document object based + * on the current JAXP parser implementation. + * + * @return Document - created DOM Document. + * @throws JDOMException when errors occur in parsing. + */ + @Override + public Document createDocument() + throws JDOMException { + + DocumentBuilder db = localbuilder.get(); + if (db == null) { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + db = dbf.newDocumentBuilder(); + localbuilder.set(db); + } catch (ParserConfigurationException e) { + throw new JDOMException("Unable to obtain a DOM parser. See cause:", e); + } + } + return db.newDocument(); + + } + +} diff --git a/core/src/java/org/jdom/adapters/XML4JDOMAdapter.java b/core/src/java/org/jdom/adapters/XML4JDOMAdapter.java new file mode 100644 index 0000000..a81ebf7 --- /dev/null +++ b/core/src/java/org/jdom/adapters/XML4JDOMAdapter.java @@ -0,0 +1,194 @@ +/*-- + + $Id: XML4JDOMAdapter.java,v 1.18 2007/11/10 05:28:59 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.adapters; + +import org.jdom.JDOMException; +import org.jdom.input.sax.BuilderErrorHandler; +import org.w3c.dom.Document; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * An adapter for the IBM XML4J DOM parser. + * + * @version $Revision: 1.18 $, $Date: 2007/11/10 05:28:59 $ + * @author Brett McLaughlin + * @author Jason Hunter + */ +public class XML4JDOMAdapter extends AbstractDOMAdapter { + + private static final String CVS_ID = + "@(#) $RCSfile: XML4JDOMAdapter.java,v $ $Revision: 1.18 $ $Date: 2007/11/10 05:28:59 $ $Name: $"; + + /** + * This creates a new {@link Document} from an + * existing InputStream by letting a DOM + * parser handle parsing using the supplied stream. + * + * @param filename file to parse. + * @param validate boolean to indicate if validation should occur. + * @return Document - instance ready for use. + * @throws IOException when I/O error occurs. + * @throws JDOMException when errors occur in parsing. + */ + public Document getDocument(File filename, boolean validate) + throws IOException, JDOMException { + + return getDocument(new FileInputStream(filename), validate); + } + + /** + * This creates a new {@link Document} from an + * existing InputStream by letting a DOM + * parser handle parsing using the supplied stream. + * + * @param in InputStream to parse. + * @param validate boolean to indicate if validation should occur. + * @return Document - instance ready for use. + * @throws IOException when I/O error occurs. + * @throws JDOMException when errors occur in parsing. + */ + public Document getDocument(InputStream in, boolean validate) + throws IOException, JDOMException { + + try { + /* + * IBM XML4J actually uses the Xerces parser, so this is + * all Xerces specific code. + */ + + // Load the parser class + Class parserClass = Class.forName("org.apache.xerces.parsers.DOMParser"); + Object parser = parserClass.newInstance(); + + // Set validation + Method setFeature = + parserClass.getMethod("setFeature", + new Class[] {java.lang.String.class, + boolean.class}); + setFeature.invoke(parser, new Object[] {"http://xml.org/sax/features/validation", + new Boolean(validate)}); + + // Set namespaces + setFeature.invoke(parser, new Object[] {"http://xml.org/sax/features/namespaces", + new Boolean(false)}); + + // Set the error handler + if (validate) { + Method setErrorHandler = + parserClass.getMethod("setErrorHandler", + new Class[] {ErrorHandler.class}); + setErrorHandler.invoke(parser, new Object[] {new BuilderErrorHandler()}); + } + + // Parse the document + Method parse = + parserClass.getMethod("parse", + new Class[] {org.xml.sax.InputSource.class}); + parse.invoke(parser, new Object[]{new InputSource(in)}); + + // Get the Document object + Method getDocument = parserClass.getMethod("getDocument", null); + Document doc = (Document)getDocument.invoke(parser, null); + + return doc; + } catch (InvocationTargetException e) { + Throwable targetException = e.getTargetException(); + if (targetException instanceof org.xml.sax.SAXParseException) { + SAXParseException parseException = (SAXParseException)targetException; + throw new JDOMException("Error on line " + parseException.getLineNumber() + + " of XML document: " + parseException.getMessage(), parseException); + } else if (targetException instanceof IOException) { + IOException ioException = (IOException) targetException; + throw ioException; + } else { + throw new JDOMException(targetException.getMessage(), targetException); + } + } catch (Exception e) { + throw new JDOMException(e.getClass().getName() + ": " + + e.getMessage(), e); + } + } + + /** + * This creates an empty Document object based + * on a specific parser implementation. + * + * @return Document - created DOM Document. + * @throws JDOMException when errors occur. + */ + public Document createDocument() throws JDOMException { + try { + return + (Document)Class.forName( + "org.apache.xerces.dom.DocumentImpl") + .newInstance(); + + } catch (Exception e) { + throw new JDOMException(e.getClass().getName() + ": " + + e.getMessage() + " while creating document", e); + } + } +} diff --git a/core/src/java/org/jdom/adapters/package.html b/core/src/java/org/jdom/adapters/package.html new file mode 100644 index 0000000..1d22aec --- /dev/null +++ b/core/src/java/org/jdom/adapters/package.html @@ -0,0 +1,7 @@ + + +Classes to create specific DOM Document instances. The DOMOutputter is the +only user of this code in JDOM, and customizing these classes is not generally +needed except in truly advanced situations. + + diff --git a/core/src/java/org/jdom/filter/AbstractFilter.java b/core/src/java/org/jdom/filter/AbstractFilter.java new file mode 100644 index 0000000..c53f931 --- /dev/null +++ b/core/src/java/org/jdom/filter/AbstractFilter.java @@ -0,0 +1,93 @@ +/*-- + + $Id: AbstractFilter.java,v 1.6 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.Content; + +/** + * Partial implementation of {@link Filter}. + * + * @author Bradley S. Huffman + * @version $Revision: 1.6 $, $Date: 2007/11/10 05:29:00 $ + */ +public abstract class AbstractFilter implements Filter { + + private static final String CVS_ID = + "@(#) $RCSfile: AbstractFilter.java,v $ $Revision: 1.6 $ $Date: 2007/11/10 05:29:00 $"; + + public Filter negate() { + return new NegateFilter(this); + } + + public Filter or(Filter filter) { + return new OrFilter(this, filter); + } + + public Filter and(Filter filter) { + return new AndFilter(this, filter); + } + + public static org.jdom.filter2.Filter toFilter2(final Filter filter) { + return new org.jdom.filter2.AbstractFilter() { + @Override + public T filter(Object content) { + //noinspection unchecked + return filter.matches(content) ? (T) content : null; + } + }; + } +} diff --git a/core/src/java/org/jdom/filter/AndFilter.java b/core/src/java/org/jdom/filter/AndFilter.java new file mode 100644 index 0000000..cc8c227 --- /dev/null +++ b/core/src/java/org/jdom/filter/AndFilter.java @@ -0,0 +1,127 @@ +/*-- + + $Id: AndFilter.java,v 1.4 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.Content; + +/** + * Allow two filters to be chained together with a logical + * and operation. + * + * @author Bradley S. Huffman + * @version $Revision: 1.4 $, $Date: 2007/11/10 05:29:00 $ + */ +final class AndFilter extends AbstractFilter { + + private static final String CVS_ID = + "@(#) $RCSfile: AndFilter.java,v $ $Revision: 1.4 $ $Date: 2007/11/10 05:29:00 $"; + + // Filter for left side of logical and. + private Filter left; + + // Filter for right side of logical and. + private Filter right; + + /** + * Match if only both supplied filters match. + * + * @param left left side of logical and + * @param right right side of logical and + * @throws IllegalArgumentException if either supplied filter is null + */ + public AndFilter(Filter left, Filter right) { + if ((left == null) || (right == null)) { + throw new IllegalArgumentException("null filter not allowed"); + } + this.left = left; + this.right = right; + } + + public boolean matches(Object obj) { + return left.matches(obj) && right.matches(obj); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof AndFilter) { + AndFilter filter = (AndFilter) obj; + if ((left.equals(filter.left) && right.equals(filter.right)) || + (left.equals(filter.right) && right.equals(filter.left))) { + return true; + } + } + return false; + } + + public int hashCode() { + return (31 * left.hashCode()) + right.hashCode(); + } + + public String toString() { + return new StringBuffer(64) + .append("[AndFilter: ") + .append(left.toString()) + .append(",\n") + .append(" ") + .append(right.toString()) + .append("]") + .toString(); + } +} diff --git a/core/src/java/org/jdom/filter/ClassFilter.java b/core/src/java/org/jdom/filter/ClassFilter.java new file mode 100644 index 0000000..a7c78aa --- /dev/null +++ b/core/src/java/org/jdom/filter/ClassFilter.java @@ -0,0 +1,109 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.Content; + +/** + * Filter input according to the input class. + * + * @author Rolf Lear + * + * @param The generic type of the filtered data. + */ +final class ClassFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + private final Class fclass; + + /** + * Construct the new ClassFilter. + * @param tclass the class instance for the generic type. + */ + public ClassFilter(Class tclass) { + fclass = tclass; + } + + @Override + public boolean matches(Object obj) { + return fclass.isInstance(obj); + } + + @Override + public String toString() { + return "[ClassFilter: Class " + fclass.getName() + "]"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ClassFilter) { + return fclass.equals(((ClassFilter)obj).fclass); + } + return false; + } + + @Override + public int hashCode() { + return fclass.hashCode(); + } + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/filter/ContentFilter.java b/core/src/java/org/jdom/filter/ContentFilter.java new file mode 100644 index 0000000..8ef2147 --- /dev/null +++ b/core/src/java/org/jdom/filter/ContentFilter.java @@ -0,0 +1,356 @@ +/*-- + + $Id: ContentFilter.java,v 1.15 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.*; + +/** + * A general purpose Filter able to represent all legal JDOM objects or a + * specific subset. Filtering is accomplished by way of a filtering mask in + * which each bit represents whether a JDOM object is visible or not. + * For example to view all Text and CDATA nodes in the content of element x. + *


+ *      Filter filter = new ContentFilter(ContentFilter.TEXT |
+ *                                        ContentFilter.CDATA);
+ *      List content = x.getContent(filter);
+ * 
+ *

+ * For those who don't like bit-masking, set methods are provided as an + * alternative. For example to allow everything except Comment nodes. + *


+ *      Filter filter =  new ContentFilter();
+ *      filter.setCommentVisible(false);
+ *      List content = x.getContent(filter);
+ * 
+ *

+ * The default is to allow all valid JDOM objects. + * + * @version $Revision: 1.15 $, $Date: 2007/11/10 05:29:00 $ + * @author Bradley S. Huffman + */ +public class ContentFilter extends AbstractFilter { + + private static final String CVS_ID = + "@(#) $RCSfile: ContentFilter.java,v $ $Revision: 1.15 $ $Date: 2007/11/10 05:29:00 $ $Name: $"; + + /** Mask for JDOM {@link Element} objects */ + public static final int ELEMENT = 1; + + /** Mask for JDOM {@link CDATA} objects */ + public static final int CDATA = 2; + + /** Mask for JDOM {@link Text} objects */ + public static final int TEXT = 4; + + /** Mask for JDOM {@link Comment} objects */ + public static final int COMMENT = 8; + + /** Mask for JDOM {@link ProcessingInstruction} objects */ + public static final int PI = 16; + + /** Mask for JDOM {@link EntityRef} objects */ + public static final int ENTITYREF = 32; + + /** Mask for JDOM {@link Document} object */ + public static final int DOCUMENT = 64; + + /** Mask for JDOM {@link DocType} object */ + public static final int DOCTYPE = 128; + + /** The JDOM object mask */ + private int filterMask; + + /** + * Default constructor that allows any legal JDOM objects. + */ + public ContentFilter() { + setDefaultMask(); + } + + /** + * Set whether all JDOM objects are visible or not. + * + * @param allVisible true all JDOM objects are visible, + * false all JDOM objects are hidden. + */ + public ContentFilter(boolean allVisible) { + if (allVisible) { + setDefaultMask(); + } + else { + filterMask &= ~filterMask; + } + } + + /** + * Filter out JDOM objects according to a filtering mask. + * + * @param mask Mask of JDOM objects to allow. + */ + public ContentFilter(int mask) { + setFilterMask(mask); + } + + /** + * Return current filtering mask. + * + * @return the current filtering mask + */ + public int getFilterMask() { + return filterMask; + } + + /** + * Set filtering mask. + * + * @param mask the new filtering mask + */ + public void setFilterMask(int mask) { + setDefaultMask(); + filterMask &= mask; + } + + /** + * Set this filter to allow all legal JDOM objects. + */ + public void setDefaultMask() { + filterMask = ELEMENT | CDATA | TEXT | COMMENT | + PI | ENTITYREF | DOCUMENT | DOCTYPE; + } + + /** + * Set filter to match only JDOM objects that are legal + * document content. + */ + public void setDocumentContent() { + filterMask = ELEMENT | COMMENT | PI | DOCTYPE; + } + + /** + * Set filter to match only JDOM objects that are legal + * element content. + */ + public void setElementContent() { + filterMask = ELEMENT | CDATA | TEXT | + COMMENT | PI | ENTITYREF; + } + + /** + * Set visiblity of Element objects. + * + * @param visible whether Elements are visible, true + * if yes, false if not + */ + public void setElementVisible(boolean visible) { + if (visible) { + filterMask |= ELEMENT; + } + else { + filterMask &= ~ELEMENT; + } + } + + /** + * Set visiblity of CDATA objects. + * + * @param visible whether CDATA nodes are visible, true + * if yes, false if not + */ + public void setCDATAVisible(boolean visible) { + if (visible) { + filterMask |= CDATA; + } + else { + filterMask &= ~CDATA; + } + } + + /** + * Set visiblity of Text objects. + * + * @param visible whether Text nodes are visible, true + * if yes, false if not + */ + public void setTextVisible(boolean visible) { + if (visible) { + filterMask |= TEXT; + } + else { + filterMask &= ~TEXT; + } + } + + /** + * Set visiblity of Comment objects. + * + * @param visible whether Comments are visible, true + * if yes, false if not + */ + public void setCommentVisible(boolean visible) { + if (visible) { + filterMask |= COMMENT; + } + else { + filterMask &= ~COMMENT; + } + } + + /** + * Set visiblity of ProcessingInstruction objects. + * + * @param visible whether ProcessingInstructions are visible, + * true if yes, false if not + */ + public void setPIVisible(boolean visible) { + if (visible) { + filterMask |= PI; + } + else { + filterMask &= ~PI; + } + } + + /** + * Set visiblity of EntityRef objects. + * + * @param visible whether EntityRefs are visible, true + * if yes, false if not + */ + public void setEntityRefVisible(boolean visible) { + if (visible) { + filterMask |= ENTITYREF; + } + else { + filterMask &= ~ENTITYREF; + } + } + + /** + * Set visiblity of DocType objects. + * + * @param visible whether the DocType is visible, true + * if yes, false if not + */ + public void setDocTypeVisible(boolean visible) { + if (visible) { + filterMask |= DOCTYPE; + } + else { + filterMask &= ~DOCTYPE; + } + } + + /** + * Check to see if the object matches according to the filter mask. + * + * @param obj The object to verify. + * @return true if the objected matched a predfined + * set of rules. + */ + public boolean matches(Object obj) { + if (obj instanceof Element) { + return (filterMask & ELEMENT) != 0; + } + else if (obj instanceof CDATA) { // must come before Text check + return (filterMask & CDATA) != 0; + } + else if (obj instanceof Text) { + return (filterMask & TEXT) != 0; + } + else if (obj instanceof Comment) { + return (filterMask & COMMENT) != 0; + } + else if (obj instanceof ProcessingInstruction) { + return (filterMask & PI) != 0; + } + else if (obj instanceof EntityRef) { + return (filterMask & ENTITYREF) != 0; + } + else if (obj instanceof Document) { + return (filterMask & DOCUMENT) != 0; + } + else if (obj instanceof DocType) { + return (filterMask & DOCTYPE) != 0; + } + + return false; + } + + /** + * Returns whether the two filters are equivalent (i.e. the + * matching mask values are identical). + * + * @param obj the object to compare against + * @return whether the two filters are equal + */ + public boolean equals(Object obj) { + // Generated by IntelliJ + if (this == obj) return true; + if (!(obj instanceof ContentFilter)) return false; + + final ContentFilter filter = (ContentFilter) obj; + + if (filterMask != filter.filterMask) return false; + + return true; + } + + public int hashCode() { + // Generated by IntelliJ + return filterMask; + } +} diff --git a/core/src/java/org/jdom/filter/ElementFilter.java b/core/src/java/org/jdom/filter/ElementFilter.java new file mode 100644 index 0000000..70c773e --- /dev/null +++ b/core/src/java/org/jdom/filter/ElementFilter.java @@ -0,0 +1,189 @@ +/*-- + + $Id: ElementFilter.java,v 1.20 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import java.io.*; +import org.jdom.*; + +/** + * A Filter that only matches {@link org.jdom.Element} objects. + * + * @version $Revision: 1.20 $, $Date: 2007/11/10 05:29:00 $ + * @author Jools Enticknap + * @author Bradley S. Huffman + */ +public class ElementFilter extends AbstractFilter { + + private static final String CVS_ID = + "@(#) $RCSfile: ElementFilter.java,v $ $Revision: 1.20 $ $Date: 2007/11/10 05:29:00 $ $Name: $"; + + /** The element name */ + private String name; + + /** The element namespace */ + private transient Namespace namespace; + + /** + * Select only the Elements. + */ + public ElementFilter() {} + + /** + * Select only the Elements with the supplied name in any Namespace. + * + * @param name The name of the Element. + */ + public ElementFilter(String name) { + this.name = name; + } + + /** + * Select only the Elements with the supplied Namespace. + * + * @param namespace The namespace the Element lives in. + */ + public ElementFilter(Namespace namespace) { + this.namespace = namespace; + } + + /** + * Select only the Elements with the supplied name and Namespace. + * + * @param name The name of the Element. + * @param namespace The namespace the Element lives in. + */ + public ElementFilter(String name, Namespace namespace) { + this.name = name; + this.namespace = namespace; + } + + /** + * Check to see if the object matches a predefined set of rules. + * + * @param obj The object to verify. + * @return true if the objected matched a predfined + * set of rules. + */ + public boolean matches(Object obj) { + if (obj instanceof Element) { + Element el = (Element) obj; + return + (this.name == null || this.name.equals(el.getName())) && + (this.namespace == null || this.namespace.equals(el.getNamespace())); + } + return false; + } + + /** + * Returns whether the two filters are equivalent (i.e. the + * matching names and namespace are equivalent). + * + * @param obj the object to compare against + * @return whether the two filters are equal + */ + public boolean equals(Object obj) { + // Generated by IntelliJ + if (this == obj) return true; + if (!(obj instanceof ElementFilter)) return false; + + final ElementFilter filter = (ElementFilter) obj; + + if (name != null ? !name.equals(filter.name) : filter.name != null) return false; + if (namespace != null ? !namespace.equals(filter.namespace) : filter.namespace != null) return false; + + return true; + } + + public int hashCode() { + // Generated by IntelliJ + int result; + result = (name != null ? name.hashCode() : 0); + result = 29 * result + (namespace != null ? namespace.hashCode() : 0); + return result; + } + + // Support a custom Namespace serialization so no two namespace + // object instances may exist for the same prefix/uri pair + private void writeObject(ObjectOutputStream out) throws IOException { + + out.defaultWriteObject(); + + // We use writeObject() and not writeUTF() to minimize space + // This allows for writing pointers to already written strings + if (namespace != null) { + out.writeObject(namespace.getPrefix()); + out.writeObject(namespace.getURI()); + } + else { + out.writeObject(null); + out.writeObject(null); + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException { + + in.defaultReadObject(); + + Object prefix = in.readObject(); + Object uri = in.readObject(); + + if (prefix != null) { // else leave namespace null here + namespace = Namespace.getNamespace((String) prefix, (String) uri); + } + } +} diff --git a/core/src/java/org/jdom/filter/Filter.java b/core/src/java/org/jdom/filter/Filter.java new file mode 100644 index 0000000..6e04303 --- /dev/null +++ b/core/src/java/org/jdom/filter/Filter.java @@ -0,0 +1,78 @@ +/*-- + + $Id: Filter.java,v 1.10 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + + +import org.jdom.Content; + +/** + * A generalized filter to restrict visibility or mutability on a list. + * + * @version $Revision: 1.10 $, $Date: 2007/11/10 05:29:00 $ + * @author Jools Enticknap + * @author Bradley S. Huffman + */ +public interface Filter extends java.io.Serializable { + /** + * Check to see if the object matches a predefined set of rules. + * + * @param obj The object to verify. + * @return true if the object matches a predfined + * set of rules. + */ + public boolean matches(Object obj); +} diff --git a/core/src/java/org/jdom/filter/Filters.java b/core/src/java/org/jdom/filter/Filters.java new file mode 100644 index 0000000..a3e7cde --- /dev/null +++ b/core/src/java/org/jdom/filter/Filters.java @@ -0,0 +1,230 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.*; + +/** + * Factory class of convenience methods to create Filter instances of common + * types. Methods that return Filters that act on core JDOM classes (Element, + * Text, etc.) are simply named after the content they return. + *

+ * Filters that + * match non-core classes (Boolean, Object, etc.) are all prefixed with the + * letter 'f' (for filter). + *

+ * The Filter returned by {@link #fpassthrough()} is not really a filter in the + * sense that it will never filter anything out - everything matches. This can + * be useful to accomplish some tasks, for example the JDOM XPath API uses it + * extensively. + * + * @author Rolf Lear + * + */ +public final class Filters { + + private static final Filter fcontent = + new ClassFilter(Content.class); + + private static final Filter fcomment = + new ClassFilter(Comment.class); + + private static final Filter fcdata = + new ClassFilter(CDATA.class); + + private static final Filter fdoctype = + new ClassFilter(DocType.class); + + private static final Filter fentityref = + new ClassFilter(EntityRef.class); + + private static final Filter fpi = + new ClassFilter(ProcessingInstruction.class); + + private static final Filter ftext = + new ClassFilter(Text.class); + + private static final Filter ftextonly = new TextOnlyFilter(); + + private static final Filter felement = + new ClassFilter(Element.class); + + + private Filters() { + // do nothing... make instances impossible. + } + + /** + * Return a Filter that matches any {@link Content} data. + * + * @return a Filter that matches any {@link Content} data. + */ + public static final Filter content() { + return fcontent; + } + + /** + * Return a Filter that matches any {@link Comment} data. + * + * @return a Filter that matches any {@link Comment} data. + */ + public static final Filter comment() { + return fcomment; + } + + /** + * Return a Filter that matches any {@link CDATA} data. + * + * @return a Filter that matches any {@link CDATA} data. + */ + public static final Filter cdata() { + return fcdata; + } + + /** + * Return a Filter that matches any {@link DocType} data. + * + * @return a Filter that matches any {@link DocType} data. + */ + public static final Filter doctype() { + return fdoctype; + } + + /** + * Return a Filter that matches any {@link EntityRef} data. + * + * @return a Filter that matches any {@link EntityRef} data. + */ + public static final Filter entityref() { + return fentityref; + } + + /** + * Return a Filter that matches any {@link Element} data. + * + * @return a Filter that matches any {@link Element} data. + */ + public static final Filter element() { + return felement; + } + + /** + * Return a Filter that matches any {@link Element} data with the specified + * name. + * + * @param name The name of Elements to match. + * @return a Filter that matches any {@link Element} data with the specified + * name. + */ + public static final Filter element(String name) { + return new ElementFilter(name, Namespace.NO_NAMESPACE); + } + + /** + * Return a Filter that matches any {@link Element} data with the specified + * name and Namespace. + * + * @param name The name of Elements to match. + * @param ns The Namespace to match + * @return a Filter that matches any {@link Element} data with the specified + * name and Namespace. + */ + public static final Filter element(String name, Namespace ns) { + return new ElementFilter(name, ns); + } + + /** + * Return a Filter that matches any {@link Element} data with the specified + * Namespace. + * + * @param ns The Namespace to match + * @return a Filter that matches any {@link Element} data with the specified + * Namespace. + */ + public static final Filter element(Namespace ns) { + return new ElementFilter(null, ns); + } + + /** + * Return a Filter that matches any {@link ProcessingInstruction} data. + * + * @return a Filter that matches any {@link ProcessingInstruction} data. + */ + public static final Filter processinginstruction() { + return fpi; + } + + /** + * Return a Filter that matches any {@link Text} data (which includes + * {@link CDATA} since that is a subclass of Text). + * + * @return a Filter that matches any {@link Text} data (which includes + * {@link CDATA} since that is a subclass of Text). + */ + public static final Filter text() { + return ftext; + } + + /** + * Return a Filter that matches any {@link Text} data (excludes + * {@link CDATA} instances). + * + * @return a Filter that matches any {@link Text} data (which excludes + * {@link CDATA} instances). + */ + public static final Filter textOnly() { + return ftextonly; + } +} diff --git a/core/src/java/org/jdom/filter/NegateFilter.java b/core/src/java/org/jdom/filter/NegateFilter.java new file mode 100644 index 0000000..80348f7 --- /dev/null +++ b/core/src/java/org/jdom/filter/NegateFilter.java @@ -0,0 +1,115 @@ +/*-- + + $Id: NegateFilter.java,v 1.4 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.Content; + +/** + * Filter that is the logical negation operation of another filter. + * + * + * @author Bradley S. Huffman + * @version $Revision: 1.4 $, $Date: 2007/11/10 05:29:00 $ + */ +final class NegateFilter extends AbstractFilter { + + private static final String CVS_ID = + "@(#) $RCSfile: NegateFilter.java,v $ $Revision: 1.4 $ $Date: 2007/11/10 05:29:00 $"; + + // Underlying filter. + private Filter filter; + + /** + * Match if the supplied filter does not match. + * + * @param filter filter to use. + */ + public NegateFilter(Filter filter) { + this.filter = filter; + } + + public boolean matches(Object obj) { + return !filter.matches(obj); + } + + public Filter negate() { + return filter; + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof NegateFilter) { + return filter.equals(((NegateFilter) obj).filter); + } + return false; + } + + public int hashCode() { + return ~filter.hashCode(); + } + + public String toString() { + return new StringBuffer(64) + .append("[NegateFilter: ") + .append(filter.toString()) + .append("]") + .toString(); + } +} diff --git a/core/src/java/org/jdom/filter/OrFilter.java b/core/src/java/org/jdom/filter/OrFilter.java new file mode 100644 index 0000000..4e715f1 --- /dev/null +++ b/core/src/java/org/jdom/filter/OrFilter.java @@ -0,0 +1,127 @@ +/*-- + + $Id: OrFilter.java,v 1.5 2007/11/10 05:29:00 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.Content; + +/** + * Allow two filters to be chained together with a logical + * or operation. + * + * @author Bradley S. Huffman + * @version $Revision: 1.5 $, $Date: 2007/11/10 05:29:00 $ + */ +final class OrFilter extends AbstractFilter { + + private static final String CVS_ID = + "@(#) $RCSfile: OrFilter.java,v $ $Revision: 1.5 $ $Date: 2007/11/10 05:29:00 $"; + + /** Filter for left side of logical or */ + private Filter left; + + /** Filter for right side of logical or */ + private Filter right; + + /** + * Match if either of the supplied filters. + * + * @param left left side of logical or + * @param right right side of logical or + * @throws IllegalArgumentException if either supplied filter is null + */ + public OrFilter(Filter left, Filter right) { + if ((left == null) || (right == null)) { + throw new IllegalArgumentException("null filter not allowed"); + } + this.left = left; + this.right = right; + } + + public boolean matches(Object obj) { + return left.matches(obj) || right.matches(obj); + } + + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof OrFilter) { + OrFilter filter = (OrFilter) obj; + if ((left.equals(filter.left) && right.equals(filter.right)) || + (left.equals(filter.right) && right.equals(filter.left))) { + return true; + } + } + return false; + } + + public int hashCode() { + return (31 * left.hashCode()) + right.hashCode(); + } + + public String toString() { + return new StringBuffer(64) + .append("[OrFilter: ") + .append(left.toString()) + .append(",\n") + .append(" ") + .append(right.toString()) + .append("]") + .toString(); + } +} diff --git a/core/src/java/org/jdom/filter/TextOnlyFilter.java b/core/src/java/org/jdom/filter/TextOnlyFilter.java new file mode 100644 index 0000000..fde304f --- /dev/null +++ b/core/src/java/org/jdom/filter/TextOnlyFilter.java @@ -0,0 +1,95 @@ +/*-- + +Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter; + +import org.jdom.Text; +import org.jdom.Content.CType; + +/** + * A filter that matches Text, but not CDATA content. + * This filter is made accessible through {@link Filters#textOnly()} + * + * @author Rolf Lear + * + */ +final class TextOnlyFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + @Override + public boolean matches(Object content) { + if (content instanceof Text) { + final Text txt = (Text)content; + if (txt.getCType() == CType.Text) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return this.getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TextOnlyFilter; + } + +} diff --git a/core/src/java/org/jdom/filter/package.html b/core/src/java/org/jdom/filter/package.html new file mode 100644 index 0000000..04bc95d --- /dev/null +++ b/core/src/java/org/jdom/filter/package.html @@ -0,0 +1,9 @@ + + +Classes to programmatically filter nodes of a document based on type, name, +value, or other aspects and to boolean and/or/negate these rules. Filters can +be used in methods like getContent(Filter) and getDescendants(Filter). A +sampling of generally useful filters are provided here. Alternate filters can +be user defined. + + diff --git a/core/src/java/org/jdom/filter2/AbstractFilter.java b/core/src/java/org/jdom/filter2/AbstractFilter.java new file mode 100644 index 0000000..bafb3b5 --- /dev/null +++ b/core/src/java/org/jdom/filter2/AbstractFilter.java @@ -0,0 +1,147 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; + +import org.jdom.Content; + +/** + * Partial implementation of {@link Filter}. + * + * @author Bradley S. Huffman + * @author Rolf Lear + * @param The Generic type of content returned by this Filter + */ +public abstract class AbstractFilter implements Filter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + @Override + public final boolean matches(Object content) { + return filter(content) != null; + } + + @Override + public List filter(List content) { + if (content == null) { + return Collections.emptyList(); + } + if (content instanceof RandomAccess) { + final int sz = content.size(); + final ArrayList ret = new ArrayList(sz); + for (int i = 0; i < sz; i++) { + final T c = filter(content.get(i)); + if (c != null) { + ret.add(c); + } + } + if (ret.isEmpty()) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(ret); + } + final ArrayList ret = new ArrayList(10); + for (Iterator it = content.iterator(); it.hasNext(); ) { + final T c = filter(it.next()); + if (c != null) { + ret.add(c); + } + } + if (ret.isEmpty()) { + return Collections.emptyList(); + } + return Collections.unmodifiableList(ret); + } + + @Override + public final Filter negate() { + if (this instanceof NegateFilter) { + return ((NegateFilter)this).getBaseFilter(); + } + return new NegateFilter(this); + } + + @Override + public final Filter or(Filter filter) { + return new OrFilter(this, filter); + } + + @Override + public final Filter and(Filter filter) { + return new AndFilter(filter, this); + } + + @Override + public Filter refine(Filter filter) { + return new AndFilter(this, filter); + } + + public static org.jdom.filter.Filter toFilter(final Filter filter) { + return new org.jdom.filter.Filter() { + @Override + public boolean matches(Object obj) { + return filter.matches(obj); + } + }; + } +} diff --git a/core/src/java/org/jdom/filter2/AndFilter.java b/core/src/java/org/jdom/filter2/AndFilter.java new file mode 100644 index 0000000..222e1cb --- /dev/null +++ b/core/src/java/org/jdom/filter2/AndFilter.java @@ -0,0 +1,123 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + + +/** + * Allow two filters to be chained together with a logical + * and operation. + * + * @author Bradley S. Huffman + * @author Rolf Lear + * @param The Generic type of content returned by this Filter +*/ +final class AndFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + private final Filter base; + private final Filter refiner; + + public AndFilter(Filter base, Filter refiner) { + if (base == null || refiner == null) { + throw new NullPointerException("Cannot have a null base or refiner filter"); + } + this.base = base; + this.refiner = refiner; + } + + @Override + public T filter(Object content) { + Object o = base.filter(content); + if (o != null) { + return refiner.filter(content); + } + return null; + } + + @Override + public int hashCode() { + return base.hashCode() ^ refiner.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof AndFilter) { + AndFilter them = (AndFilter)obj; + return (base.equals(them.base) && refiner.equals(them.refiner)) || + (refiner.equals(them.base) && base.equals(them.refiner)); + } + + return false; + } + + @Override + public String toString() { + return new StringBuilder(64) + .append("[AndFilter: ") + .append(base.toString()) + .append(",\n") + .append(" ") + .append(refiner.toString()) + .append("]") + .toString(); + } +} diff --git a/core/src/java/org/jdom/filter2/AttributeFilter.java b/core/src/java/org/jdom/filter2/AttributeFilter.java new file mode 100644 index 0000000..62e823c --- /dev/null +++ b/core/src/java/org/jdom/filter2/AttributeFilter.java @@ -0,0 +1,171 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import org.jdom.Attribute; +import org.jdom.Namespace; + +/** + * A Filter that only matches {@link org.jdom.Attribute} objects. + * + * @author Rolf Lear + */ +public class AttributeFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + /** The element name */ + private final String name; + + /** The element namespace */ + private final Namespace namespace; + + /** + * Select only the Elements. + */ + public AttributeFilter() { + this(null,null); + } + + /** + * Select only the Elements with the supplied name in any Namespace. + * + * @param name The name of the Element. + */ + public AttributeFilter(String name) { + this(name, null); + } + + /** + * Select only the Attributes with the supplied Namespace. + * + * @param namespace The namespace the Attribute lives in. + */ + public AttributeFilter(Namespace namespace) { + this(null, namespace); + } + + /** + * Select only the Attributes with the supplied name and Namespace. + * + * @param name The name of the Attribute. + * @param namespace The namespace the Attribute lives in. + */ + public AttributeFilter(String name, Namespace namespace) { + this.name = name; + this.namespace = namespace; + } + + /** + * Check to see if the Content matches a predefined set of rules. + * + * @param content The Content to verify. + * @return true if the objected matched a predfined + * set of rules. + */ + @Override + public Attribute filter(Object content) { + if (content instanceof Attribute) { + Attribute att = (Attribute) content; + if (name == null) { + if (namespace == null) { + return att; + } + return namespace.equals(att.getNamespace()) ? att : null; + } + if (!name.equals(att.getName())) { + return null; + } + if (namespace == null) { + return att; + } + return namespace.equals(att.getNamespace()) ? att : null; + } + return null; + } + + /** + * Returns whether the two filters are equivalent (i.e. the + * matching names and namespace are equivalent). + * + * @param obj the object to compare against + * @return whether the two filters are equal + */ + @Override + public boolean equals(Object obj) { + // Generated by IntelliJ + if (this == obj) return true; + if (!(obj instanceof AttributeFilter)) return false; + + final AttributeFilter filter = (AttributeFilter) obj; + + if (name != null ? !name.equals(filter.name) : filter.name != null) return false; + if (namespace != null ? !namespace.equals(filter.namespace) : filter.namespace != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result; + result = (name != null ? name.hashCode() : 0); + result = 29 * result + (namespace != null ? namespace.hashCode() : 0); + return result; + } + +} diff --git a/core/src/java/org/jdom/filter2/ClassFilter.java b/core/src/java/org/jdom/filter2/ClassFilter.java new file mode 100644 index 0000000..f9880f8 --- /dev/null +++ b/core/src/java/org/jdom/filter2/ClassFilter.java @@ -0,0 +1,107 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +/** + * Filter input according to the input class. + * + * @author Rolf Lear + * + * @param The generic type of the filtered data. + */ +final class ClassFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + private final Class fclass; + + /** + * Construct the new ClassFilter. + * @param tclass the class instance for the generic type. + */ + public ClassFilter(Class tclass) { + fclass = tclass; + } + + @Override + public T filter(Object content) { + return fclass.isInstance(content) ? fclass.cast(content) : null; + } + + @Override + public String toString() { + return "[ClassFilter: Class " + fclass.getName() + "]"; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof ClassFilter) { + return fclass.equals(((ClassFilter)obj).fclass); + } + return false; + } + + @Override + public int hashCode() { + return fclass.hashCode(); + } + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/filter2/ContentFilter.java b/core/src/java/org/jdom/filter2/ContentFilter.java new file mode 100644 index 0000000..0591bd8 --- /dev/null +++ b/core/src/java/org/jdom/filter2/ContentFilter.java @@ -0,0 +1,364 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import org.jdom.*; + +/** + * A general purpose Filter able to represent all legal JDOM objects or a + * specific subset. Filtering is accomplished by way of a filtering mask in + * which each bit represents whether a JDOM object is visible or not. + * For example to view all Text and CDATA nodes in the content of element x. + *


+ *      Filter filter = new ContentFilter(ContentFilter.TEXT |
+ *                                        ContentFilter.CDATA);
+ *      List content = x.getContent(filter);
+ * 
+ *

+ * For those who don't like bit-masking, set methods are provided as an + * alternative. For example to allow everything except Comment nodes. + *


+ *      Filter filter =  new ContentFilter();
+ *      filter.setCommentVisible(false);
+ *      List content = x.getContent(filter);
+ * 
+ *

+ * The default is to allow all valid JDOM objects. + * + * @author Bradley S. Huffman + */ +public class ContentFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + /** Mask for JDOM {@link Element} objects */ + public static final int ELEMENT = 1; + + /** Mask for JDOM {@link CDATA} objects */ + public static final int CDATA = 2; + + /** Mask for JDOM {@link Text} objects */ + public static final int TEXT = 4; + + /** Mask for JDOM {@link Comment} objects */ + public static final int COMMENT = 8; + + /** Mask for JDOM {@link ProcessingInstruction} objects */ + public static final int PI = 16; + + /** Mask for JDOM {@link EntityRef} objects */ + public static final int ENTITYREF = 32; + + /** Mask for JDOM {@link Document} object */ + public static final int DOCUMENT = 64; + + /** Mask for JDOM {@link DocType} object */ + public static final int DOCTYPE = 128; + + /** The JDOM object mask */ + private int filterMask; + + /** + * Default constructor that allows any legal JDOM objects. + */ + public ContentFilter() { + setDefaultMask(); + } + + /** + * Set whether all JDOM objects are visible or not. + * + * @param allVisible true all JDOM objects are visible, + * false all JDOM objects are hidden. + */ + public ContentFilter(boolean allVisible) { + if (allVisible) { + setDefaultMask(); + } + else { + filterMask &= ~filterMask; + } + } + + /** + * Filter out JDOM objects according to a filtering mask. + * + * @param mask Mask of JDOM objects to allow. + */ + public ContentFilter(int mask) { + setFilterMask(mask); + } + + /** + * Return current filtering mask. + * + * @return the current filtering mask + */ + public int getFilterMask() { + return filterMask; + } + + /** + * Set filtering mask. + * + * @param mask the new filtering mask + */ + public void setFilterMask(int mask) { + setDefaultMask(); + filterMask &= mask; + } + + /** + * Set this filter to allow all legal JDOM objects. + */ + public void setDefaultMask() { + filterMask = ELEMENT | CDATA | TEXT | COMMENT | + PI | ENTITYREF | DOCUMENT | DOCTYPE; + } + + /** + * Set filter to match only JDOM objects that are legal + * document content. + */ + public void setDocumentContent() { + filterMask = ELEMENT | COMMENT | PI | DOCTYPE; + } + + /** + * Set filter to match only JDOM objects that are legal + * element content. + */ + public void setElementContent() { + filterMask = ELEMENT | CDATA | TEXT | + COMMENT | PI | ENTITYREF; + } + + /** + * Set visiblity of Element objects. + * + * @param visible whether Elements are visible, true + * if yes, false if not + */ + public void setElementVisible(boolean visible) { + if (visible) { + filterMask |= ELEMENT; + } + else { + filterMask &= ~ELEMENT; + } + } + + /** + * Set visiblity of CDATA objects. + * + * @param visible whether CDATA nodes are visible, true + * if yes, false if not + */ + public void setCDATAVisible(boolean visible) { + if (visible) { + filterMask |= CDATA; + } + else { + filterMask &= ~CDATA; + } + } + + /** + * Set visiblity of Text objects. + * + * @param visible whether Text nodes are visible, true + * if yes, false if not + */ + public void setTextVisible(boolean visible) { + if (visible) { + filterMask |= TEXT; + } + else { + filterMask &= ~TEXT; + } + } + + /** + * Set visiblity of Comment objects. + * + * @param visible whether Comments are visible, true + * if yes, false if not + */ + public void setCommentVisible(boolean visible) { + if (visible) { + filterMask |= COMMENT; + } + else { + filterMask &= ~COMMENT; + } + } + + /** + * Set visiblity of ProcessingInstruction objects. + * + * @param visible whether ProcessingInstructions are visible, + * true if yes, false if not + */ + public void setPIVisible(boolean visible) { + if (visible) { + filterMask |= PI; + } + else { + filterMask &= ~PI; + } + } + + /** + * Set visiblity of EntityRef objects. + * + * @param visible whether EntityRefs are visible, true + * if yes, false if not + */ + public void setEntityRefVisible(boolean visible) { + if (visible) { + filterMask |= ENTITYREF; + } + else { + filterMask &= ~ENTITYREF; + } + } + + /** + * Set visiblity of DocType objects. + * + * @param visible whether the DocType is visible, true + * if yes, false if not + */ + public void setDocTypeVisible(boolean visible) { + if (visible) { + filterMask |= DOCTYPE; + } + else { + filterMask &= ~DOCTYPE; + } + } + + /** + * Check to see if the object matches according to the filter mask. + * + * @param obj The object to verify. + * @return true if the objected matched a predfined + * set of rules. + */ + @Override + public Content filter(Object obj) { + if (obj == null || !Content.class.isInstance(obj)) { + return null; + } + + Content content = (Content)obj; + + if (content instanceof Element) { + return (filterMask & ELEMENT) != 0 ? content : null; + } + else if (content instanceof CDATA) { // must come before Text check + return (filterMask & CDATA) != 0 ? content : null; + } + else if (content instanceof Text) { + return (filterMask & TEXT) != 0 ? content : null; + } + else if (content instanceof Comment) { + return (filterMask & COMMENT) != 0 ? content : null; + } + else if (content instanceof ProcessingInstruction) { + return (filterMask & PI) != 0 ? content : null; + } + else if (content instanceof EntityRef) { + return (filterMask & ENTITYREF) != 0 ? content : null; + } + // else if (obj instanceof Document) { + // return (filterMask & DOCUMENT) != 0 ? obj : null; + // } + else if (content instanceof DocType) { + return (filterMask & DOCTYPE) != 0 ? content : null; + } + + return null; + } + + /** + * Returns whether the two filters are equivalent (i.e. the + * matching mask values are identical). + * + * @param obj the object to compare against + * @return whether the two filters are equal + */ + @Override + public boolean equals(Object obj) { + // Generated by IntelliJ + if (this == obj) return true; + if (!(obj instanceof ContentFilter)) return false; + + final ContentFilter filter = (ContentFilter) obj; + + if (filterMask != filter.filterMask) return false; + + return true; + } + + @Override + public int hashCode() { + // Generated by IntelliJ + return filterMask; + } +} diff --git a/core/src/java/org/jdom/filter2/ElementFilter.java b/core/src/java/org/jdom/filter2/ElementFilter.java new file mode 100644 index 0000000..67849f7 --- /dev/null +++ b/core/src/java/org/jdom/filter2/ElementFilter.java @@ -0,0 +1,178 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import org.jdom.Element; +import org.jdom.Namespace; + +/** + * A Filter that only matches {@link org.jdom.Element} objects. + * + * @author Jools Enticknap + * @author Bradley S. Huffman + */ +public class ElementFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + /** The element name */ + private String name; + + /** The element namespace */ + private Namespace namespace; + + /** + * Select only the Elements. + */ + public ElementFilter() {} + + /** + * Select only the Elements with the supplied name in any Namespace. + * + * @param name The name of the Element. + */ + public ElementFilter(String name) { + this.name = name; + } + + /** + * Select only the Elements with the supplied Namespace. + * + * @param namespace The namespace the Element lives in. + */ + public ElementFilter(Namespace namespace) { + this.namespace = namespace; + } + + /** + * Select only the Elements with the supplied name and Namespace. + * + * @param name The name of the Element. + * @param namespace The namespace the Element lives in. + */ + public ElementFilter(String name, Namespace namespace) { + this.name = name; + this.namespace = namespace; + } + + /** + * Check to see if the object matches a predefined set of rules. + * + * @param content The object to verify. + * @return true if the objected matched a predfined + * set of rules. + */ + @Override + public Element filter(Object content) { + if (content instanceof Element) { + Element el = (Element) content; + if (name == null) { + if (namespace == null) { + return el; + } + return namespace.equals(el.getNamespace()) ? el : null; + } + if (!name.equals(el.getName())) { + return null; + } + if (namespace == null) { + return el; + } + return namespace.equals(el.getNamespace()) ? el : null; + } + return null; + } + + /** + * Returns whether the two filters are equivalent (i.e. the + * matching names and namespace are equivalent). + * + * @param obj the object to compare against + * @return whether the two filters are equal + */ + @Override + public boolean equals(Object obj) { + // Generated by IntelliJ + if (this == obj) return true; + if (!(obj instanceof ElementFilter)) return false; + + final ElementFilter filter = (ElementFilter) obj; + + if (name != null ? !name.equals(filter.name) : filter.name != null) return false; + if (namespace != null ? !namespace.equals(filter.namespace) : filter.namespace != null) return false; + + return true; + } + + @Override + public int hashCode() { + // Generated by IntelliJ + int result; + result = (name != null ? name.hashCode() : 0); + result = 29 * result + (namespace != null ? namespace.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "[ElementFilter: Name " + (name == null ? "*any*" : name) + + " with Namespace " + namespace + "]"; + } + + +} diff --git a/core/src/java/org/jdom/filter2/Filter.java b/core/src/java/org/jdom/filter2/Filter.java new file mode 100644 index 0000000..5486679 --- /dev/null +++ b/core/src/java/org/jdom/filter2/Filter.java @@ -0,0 +1,134 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import java.util.List; + + +/** + * A generalized filter to restrict visibility or mutability on a list. + * + * @author Jools Enticknap + * @author Bradley S. Huffman + * @author Rolf Lear + * @param The Generic type of content returned by this Filter + */ +public interface Filter extends java.io.Serializable { + + + /** + * Filter the input list keeping only the items that match the Filter. + * + * @param content The content to filter. + * @return a new read-only RandomAccess list of the filtered input content. + */ + public List filter(List content); + + /** + * Check to see if the content matches this Filter. + * If it does, return the content cast as this filter's return type, + * otherwise return null. + * @param content The content to test. + * @return The content if it matches the filter, cast as this Filter's type. + */ + public T filter(Object content); + + /** + * Check to see if the object matches a predefined set of rules. + * + * @param content The object to verify. + * @return true if the object matches a predfined + * set of rules. + */ + public boolean matches(Object content); + + + /** + * Creates an 'inverse' filter + * @return a Filter that returns all content except what this Filter + * instance would. + */ + public Filter negate(); + + /** + * Creates an ORing filter + * @param filter a second Filter to OR with. + * @return a new Filter instance that returns the 'union' of this filter and + * the specified filter. + */ + public Filter or(Filter filter); + + /** + * Creates an ANDing filter. The generic type of the result is the same as + * this Filter. + * + * @param filter a second Filter to AND with. + * @return a new Filter instance that returns the 'intersection' of this + * filter and the specified filter. + */ + public Filter and(Filter filter); + + /** + * This is similar to the and(Filter) method except the generic type is + * different. + * @param The Generic type of the returned data is taken from the input + * instance. + * @param filter The filter to refine our results with. + * @return A Filter that requires content to both match our instance and the + * refining instance, but the generic type of the retuned data is based + * on the refining instance, not this instance. + */ + public Filter refine(Filter filter); +} diff --git a/core/src/java/org/jdom/filter2/Filters.java b/core/src/java/org/jdom/filter2/Filters.java new file mode 100644 index 0000000..fefe1d1 --- /dev/null +++ b/core/src/java/org/jdom/filter2/Filters.java @@ -0,0 +1,361 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; + +/** + * Factory class of convenience methods to create Filter instances of common + * types. Methods that return Filters that act on core JDOM classes (Element, + * Text, etc.) are simply named after the content they return. + *

+ * Filters that + * match non-core classes (Boolean, Object, etc.) are all prefixed with the + * letter 'f' (for filter). + *

+ * The Filter returned by {@link #fpassthrough()} is not really a filter in the + * sense that it will never filter anything out - everything matches. This can + * be useful to accomplish some tasks, for example the JDOM XPath API uses it + * extensively. + * + * @author Rolf Lear + * + */ +public final class Filters { + + private static final Filter fcontent = + new ClassFilter(Content.class); + + private static final Filter fattribute = + new AttributeFilter(); + + private static final Filter fcomment = + new ClassFilter(Comment.class); + + private static final Filter fcdata = + new ClassFilter(CDATA.class); + + private static final Filter fdoctype = + new ClassFilter(DocType.class); + + private static final Filter fentityref = + new ClassFilter(EntityRef.class); + + private static final Filter fpi = + new ClassFilter(ProcessingInstruction.class); + + private static final Filter ftext = + new ClassFilter(Text.class); + + private static final Filter ftextonly = new TextOnlyFilter(); + + private static final Filter felement = + new ClassFilter(Element.class); + + private static final Filter fdocument = + new ClassFilter(Document.class); + + private static final Filter fdouble = + new ClassFilter(Double.class); + + private static final Filter fboolean = + new ClassFilter(Boolean.class); + + private static final Filter fstring = + new ClassFilter(String.class); + + private static final Filter fpassthrough = + new PassThroughFilter(); + + + private Filters() { + // do nothing... make instances impossible. + } + + /** + * Return a Filter that matches any {@link Content} data. + * + * @return a Filter that matches any {@link Content} data. + */ + public static final Filter content() { + return fcontent; + } + + /** + * Return a Filter that matches any {@link Attribute} data. + * + * @return a Filter that matches any {@link Attribute} data. + */ + public static final Filter attribute() { + return fattribute; + } + + /** + * Return a Filter that matches any {@link Attribute} data with the + * specified name. + * + * @param name The name for all the Attributes to have (these can be in any + * Namespace). + * @return a Filter that matches any {@link Attribute} data with the + * specified name. + */ + public static final Filter attribute(String name) { + return new AttributeFilter(name); + } + + /** + * Return a Filter that matches any {@link Attribute} data with the + * specified name and namespace. + * + * @param name The name for all the Attributes to have. + * @param ns The Namespace for all the Attributes to have. + * @return a Filter that matches any {@link Attribute} data with the + * specified name and namespace. + */ + public static final Filter attribute(String name, Namespace ns) { + return new AttributeFilter(name, ns); + } + + /** + * Return a Filter that matches any {@link Attribute} data with the + * specified namespace. + * + * @param ns The Namespace for all the Attributes to have. + * @return a Filter that matches any {@link Attribute} data with the + * specified namespace. + */ + public static final Filter attribute(Namespace ns) { + return new AttributeFilter(ns); + } + + /** + * Return a Filter that matches any {@link Comment} data. + * + * @return a Filter that matches any {@link Comment} data. + */ + public static final Filter comment() { + return fcomment; + } + + /** + * Return a Filter that matches any {@link CDATA} data. + * + * @return a Filter that matches any {@link CDATA} data. + */ + public static final Filter cdata() { + return fcdata; + } + + /** + * Return a Filter that matches any {@link DocType} data. + * + * @return a Filter that matches any {@link DocType} data. + */ + public static final Filter doctype() { + return fdoctype; + } + + /** + * Return a Filter that matches any {@link EntityRef} data. + * + * @return a Filter that matches any {@link EntityRef} data. + */ + public static final Filter entityref() { + return fentityref; + } + + /** + * Return a Filter that matches any {@link Element} data. + * + * @return a Filter that matches any {@link Element} data. + */ + public static final Filter element() { + return felement; + } + + /** + * Return a Filter that matches any {@link Document} data. + * + * @return a Filter that matches any {@link Document} data. + */ + public static final Filter document() { + return fdocument; + } + + /** + * Return a Filter that matches any {@link Element} data with the specified + * name. + * + * @param name The name of Elements to match. + * @return a Filter that matches any {@link Element} data with the specified + * name. + */ + public static final Filter element(String name) { + return new ElementFilter(name, Namespace.NO_NAMESPACE); + } + + /** + * Return a Filter that matches any {@link Element} data with the specified + * name and Namespace. + * + * @param name The name of Elements to match. + * @param ns The Namespace to match + * @return a Filter that matches any {@link Element} data with the specified + * name and Namespace. + */ + public static final Filter element(String name, Namespace ns) { + return new ElementFilter(name, ns); + } + + /** + * Return a Filter that matches any {@link Element} data with the specified + * Namespace. + * + * @param ns The Namespace to match + * @return a Filter that matches any {@link Element} data with the specified + * Namespace. + */ + public static final Filter element(Namespace ns) { + return new ElementFilter(null, ns); + } + + /** + * Return a Filter that matches any {@link ProcessingInstruction} data. + * + * @return a Filter that matches any {@link ProcessingInstruction} data. + */ + public static final Filter processinginstruction() { + return fpi; + } + + /** + * Return a Filter that matches any {@link Text} data (which includes + * {@link CDATA} since that is a subclass of Text). + * + * @return a Filter that matches any {@link Text} data (which includes + * {@link CDATA} since that is a subclass of Text). + */ + public static final Filter text() { + return ftext; + } + + /** + * Return a Filter that matches any {@link Text} data (excludes + * {@link CDATA} instances). + * + * @return a Filter that matches any {@link Text} data (which excludes + * {@link CDATA} instances). + */ + public static final Filter textOnly() { + return ftextonly; + } + + /** + * Return a Filter that matches any Boolean data. + * + * @return a Filter that matches any Boolean data. + */ + public static final Filter fboolean() { + return fboolean; + } + + /** + * Return a Filter that matches any String data. + * + * @return a Filter that matches any String data. + */ + public static final Filter fstring() { + return fstring; + } + + /** + * Return a Filter that matches any Double data. + * + * @return a Filter that matches any Double data. + */ + public static final Filter fdouble() { + return fdouble; + } + + /** + * Return a Filter that matches any data of the specified Class. + * + * @param The generic type of the content returned by this Filter + * @param clazz the Class type to match in the filter + * @return a Filter that matches any data of the specified Class. + */ + public static final Filter fclass(Class clazz) { + return new ClassFilter(clazz); + } + + /** + * Return a filter that does no filtering at all - everything matches. + * @return A Pass-Through Filter. + */ + public static final Filter fpassthrough() { + return fpassthrough; + } + +} diff --git a/core/src/java/org/jdom/filter2/NegateFilter.java b/core/src/java/org/jdom/filter2/NegateFilter.java new file mode 100644 index 0000000..4d1b689 --- /dev/null +++ b/core/src/java/org/jdom/filter2/NegateFilter.java @@ -0,0 +1,120 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + + +/** + * Filter that is the logical negation operation of another filter. + * + * + * @author Bradley S. Huffman + */ +final class NegateFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + // Underlying filter. + private final Filter filter; + + /** + * Match if the supplied filter does not match. + * + * @param filter filter to use. + */ + public NegateFilter(Filter filter) { + this.filter = filter; + } + + @Override + public Object filter(Object content) { + if (filter.matches(content)) { + return null; + } + return content; + } + + Filter getBaseFilter() { + return filter; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof NegateFilter) { + return filter.equals(((NegateFilter) obj).filter); + } + return false; + } + + @Override + public int hashCode() { + return ~filter.hashCode(); + } + + @Override + public String toString() { + return new StringBuilder(64) + .append("[NegateFilter: ") + .append(filter.toString()) + .append("]") + .toString(); + } +} diff --git a/core/src/java/org/jdom/filter2/OrFilter.java b/core/src/java/org/jdom/filter2/OrFilter.java new file mode 100644 index 0000000..8043e14 --- /dev/null +++ b/core/src/java/org/jdom/filter2/OrFilter.java @@ -0,0 +1,133 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import org.jdom.Content; + +/** + * Allow two filters to be chained together with a logical + * or operation. + * + * @author Bradley S. Huffman + */ +final class OrFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + /** Filter for left side of logical or */ + private final Filter left; + + /** Filter for right side of logical or */ + private final Filter right; + + /** + * Match if either of the supplied filters. + * + * @param left left side of logical or + * @param right right side of logical or + * @throws IllegalArgumentException if either supplied filter is null + */ + public OrFilter(Filter left, Filter right) { + if ((left == null) || (right == null)) { + throw new IllegalArgumentException("null filter not allowed"); + } + this.left = left; + this.right = right; + } + + @Override + public Content filter(Object obj) { + if (left.matches(obj) || right.matches(obj)) { + return (Content)obj; + } + return null; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof OrFilter) { + OrFilter filter = (OrFilter) obj; + if ((left.equals(filter.left) && right.equals(filter.right)) || + (left.equals(filter.right) && right.equals(filter.left))) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + return ~(left.hashCode()) ^ right.hashCode(); + } + + @Override + public String toString() { + return new StringBuilder(64) + .append("[OrFilter: ") + .append(left.toString()) + .append(",\n") + .append(" ") + .append(right.toString()) + .append("]") + .toString(); + } +} diff --git a/core/src/java/org/jdom/filter2/PassThroughFilter.java b/core/src/java/org/jdom/filter2/PassThroughFilter.java new file mode 100644 index 0000000..9b85c36 --- /dev/null +++ b/core/src/java/org/jdom/filter2/PassThroughFilter.java @@ -0,0 +1,97 @@ +/*-- + +Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; + +/** + * A filter that does not actual filtering. + * This is made available through {@link Filters#fpassthrough()}. + * + * @author Rolf Lear + * + */ +final class PassThroughFilter extends AbstractFilter { + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + + @Override + public Object filter(Object content) { + return content; + } + + @Override + public List filter(List content) { + if (content == null || content.isEmpty()) { + return Collections.emptyList(); + } + if (content instanceof RandomAccess) { + return Collections.unmodifiableList(content); + } + ArrayList ret = new ArrayList(); + for (Iterator it = content.iterator(); it.hasNext(); ) { + ret.add(it.next()); + } + return Collections.unmodifiableList(ret); + } + +} diff --git a/core/src/java/org/jdom/filter2/TextOnlyFilter.java b/core/src/java/org/jdom/filter2/TextOnlyFilter.java new file mode 100644 index 0000000..4986dd7 --- /dev/null +++ b/core/src/java/org/jdom/filter2/TextOnlyFilter.java @@ -0,0 +1,95 @@ +/*-- + +Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.filter2; + +import org.jdom.Text; +import org.jdom.Content.CType; + +/** + * A filter that matches Text, but not CDATA content. + * This filter is made accessible through {@link Filters#textOnly()} + * + * @author Rolf Lear + * + */ +final class TextOnlyFilter extends AbstractFilter { + + /** + * JDOM2 Serialization: Default mechanism + */ + private static final long serialVersionUID = 200L; + + @Override + public Text filter(Object content) { + if (content instanceof Text) { + final Text txt = (Text)content; + if (txt.getCType() == CType.Text) { + return txt; + } + } + return null; + } + + @Override + public int hashCode() { + return this.getClass().hashCode(); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof TextOnlyFilter; + } + +} diff --git a/core/src/java/org/jdom/filter2/package.html b/core/src/java/org/jdom/filter2/package.html new file mode 100644 index 0000000..25c2b80 --- /dev/null +++ b/core/src/java/org/jdom/filter2/package.html @@ -0,0 +1,11 @@ + + Classes to both filter and generically type-cast nodes of a document + based on type, name, value, or other aspects, and to boolean + AND/OR/NEGATE these rules. Filters can be used in methods like + getContent(Filter) and getDescendants(Filter). Filters are also used + extensively in the XPath API. The Filters class provides access + to a large number of useful filters, and also a sampling of generally useful + filters is provided here. Additional filters can be user defined, and that + is made easier by extending thr AbstractFilter class. + + diff --git a/core/src/java/org/jdom/input/DOMBuilder.java b/core/src/java/org/jdom/input/DOMBuilder.java new file mode 100644 index 0000000..a1cb79a --- /dev/null +++ b/core/src/java/org/jdom/input/DOMBuilder.java @@ -0,0 +1,472 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input; + +/* + * To keep things simple, all DOM-based items are fully qualified in this code. + * As such, there are no import org.w3c.dom.* statements... + * This way there isless confusion about what a Document or Element is.... + */ + +import static org.jdom.JDOMConstants.*; + +import java.util.HashMap; + +import org.jdom.Attribute; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; + + +/** + * Builds a JDOM Document from a pre-existing DOM {@link org.w3c.dom.Document + * org.w3c.dom.Document}. + *

+ * If you are building a document that has Namespace declarations, you should + * ensure that the Namespaces are correctly recorded in the DOM document before + * building the JDOM document from the DOM. By default, the native Java + * DOMBuilderFactory is configured to ignore Namespaces, and thus they are + * 'lost' in the DOM tree. JDOM expects Namespace-aware documents, so you + * should ensure that you change the default settings on the + * DOMBuilderFactory before parsing the DOM document. For example: + *

+ *

+ *     DocumentBuilderFactory domfactory = DocumentBuilderFactory.newInstance();
+ *     domfactory.setNamespaceAware(true);
+ *     DocumentBuilder dombuilder = domfac.newDocumentBuilder();
+ *     org.w3c.dom.Document doc = dombuilder.parse(....);
+ * 
+ * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Philip Nelson + * @author Kevin Regan + * @author Yusuf Goolamabbas + * @author Dan Schaffer + * @author Bradley S. Huffman + */ +public class DOMBuilder { + + /** The factory for creating new JDOM objects */ + private JDOMFactory factory = new DefaultJDOMFactory(); + + /** + * This creates a new DOMBuilder instance using the DefaultJDOMFactory + * to build the JDOM content. + */ + public DOMBuilder() { + } + + /** + * This sets a custom JDOMFactory for the builder. Use this to build + * the tree with your own subclasses of the JDOM classes. + * + * @param factory JDOMFactory to use + */ + public void setFactory(JDOMFactory factory) { + this.factory = factory; + } + + /** + * Returns the current {@link org.jdom.JDOMFactory} in use. + * @return the factory in use + */ + public JDOMFactory getFactory() { + return factory; + } + + /** + * This will build a JDOM tree from an existing DOM tree. + * + * @param domDocument org.w3c.dom.Document object + * @return Document - JDOM document object. + */ + public Document build(org.w3c.dom.Document domDocument) { + Document doc = factory.document(null); + buildTree(domDocument, doc, null, true); + return doc; + } + + /** + * This will build a JDOM Element from an existing DOM Element + * + * @param domElement org.w3c.dom.Element object + * @return Element - JDOM Element object + */ + public org.jdom.Element build(org.w3c.dom.Element domElement) { + Document doc = factory.document(null); + buildTree(domElement, doc, null, true); + return doc.getRootElement(); + } + + /** + * This will build a JDOM CDATA from an existing DOM CDATASection + * + * @param cdata org.w3c.dom.CDATASection object + * @return CDATA - JDOM CDATA object + * @since JDOM2 + */ + public org.jdom.CDATA build(org.w3c.dom.CDATASection cdata) { + return factory.cdata(cdata.getNodeValue()); + } + + /** + * This will build a JDOM Text from an existing DOM Text + * + * @param text org.w3c.dom.Text object + * @return Text - JDOM Text object + * @since JDOM2 + */ + public org.jdom.Text build(org.w3c.dom.Text text) { + return factory.text(text.getNodeValue()); + } + + /** + * This will build a JDOM Comment from an existing DOM Comment + * + * @param comment org.w3c.dom.Comment object + * @return Comment - JDOM Comment object + * @since JDOM2 + */ + public org.jdom.Comment build(org.w3c.dom.Comment comment) { + return factory.comment(comment.getNodeValue()); + } + + /** + * This will build a JDOM ProcessingInstruction from an existing DOM ProcessingInstruction + * + * @param pi org.w3c.dom.ProcessingInstruction object + * @return ProcessingInstruction - JDOM ProcessingInstruction object + * @since JDOM2 + */ + public org.jdom.ProcessingInstruction build(org.w3c.dom.ProcessingInstruction pi) { + return factory.processingInstruction(pi.getTarget(), pi.getData()); + } + + /** + * This will build a JDOM EntityRef from an existing DOM EntityReference + * + * @param er org.w3c.dom.EntityReference object + * @return EnityRef - JDOM EntityRef object + * @since JDOM2 + */ + public org.jdom.EntityRef build(org.w3c.dom.EntityReference er) { + return factory.entityRef(er.getNodeName()); + } + + /** + * This will build a JDOM Element from an existing DOM Element + * + * @param doctype org.w3c.dom.Element object + * @return Element - JDOM Element object + * @since JDOM2 + */ + public org.jdom.DocType build(org.w3c.dom.DocumentType doctype) { + String publicID = doctype.getPublicId(); + String systemID = doctype.getSystemId(); + String internalDTD = doctype.getInternalSubset(); + + DocType docType = factory.docType(doctype.getName()); + docType.setPublicID(publicID); + docType.setSystemID(systemID); + docType.setInternalSubset(internalDTD); + return docType; + } + + + + /** + * This takes a DOM Node and builds up + * a JDOM tree, recursing until the DOM tree is exhausted + * and the JDOM tree results. + * + * @param node Code to examine. + * @param doc JDOM Document being built. + * @param current Element that is current parent. + * @param atRoot boolean indicating whether at root level. + */ + private void buildTree(org.w3c.dom.Node node, + Document doc, + Element current, + boolean atRoot) { + // Recurse through the tree + switch (node.getNodeType()) { + case org.w3c.dom.Node.DOCUMENT_NODE: + org.w3c.dom.NodeList nodes = node.getChildNodes(); + for (int i=0, size=nodes.getLength(); i= 0) { + prefix = nodeName.substring(0, colon); + localName = nodeName.substring(colon + 1); + } + + // Get element's namespace + Namespace ns = null; + String uri = node.getNamespaceURI(); + if (uri == null) { + ns = (current == null) ? Namespace.NO_NAMESPACE + : current.getNamespace(prefix); + } + else { + ns = Namespace.getNamespace(prefix, uri); + } + + Element element = factory.element(localName, ns); + + if (atRoot) { + // If at root, set as document root + factory.setRoot(doc, element); + } else { + // else add to parent element + factory.addContent(current, element); + } + + // Add namespaces + org.w3c.dom.NamedNodeMap attributeList = node.getAttributes(); + int attsize = attributeList.getLength(); + + for (int i = 0; i < attsize; i++) { + org.w3c.dom.Attr att = (org.w3c.dom.Attr) attributeList.item(i); + + String attname = att.getName(); + if (attname.startsWith(NS_PREFIX_XMLNS)) { + String attPrefix = NS_PREFIX_DEFAULT; + colon = attname.indexOf(':'); + if (colon >= 0) { + attPrefix = attname.substring(colon + 1); + } + + String attvalue = att.getValue(); + + Namespace declaredNS = + Namespace.getNamespace(attPrefix, attvalue); + + // Add as additional namespaces if it's different + // to this element's namespace (perhaps we should + // also have logic not to mark them as additional if + // it's been done already, but it probably doesn't + // matter) + if (prefix.equals(attPrefix)) { + // RL: note, it should also be true that uri.equals(attvalue) + // if not, then the parser is boken. + // further, declaredNS should be exactly the same as ns + // so the following should in fact do nothing. + element.setNamespace(declaredNS); + } + else { + factory.addNamespaceDeclaration(element, declaredNS); + } + } + } + + // Add attributes + for (int i = 0; i < attsize; i++) { + org.w3c.dom.Attr att = (org.w3c.dom.Attr) attributeList.item(i); + + String attname = att.getName(); + + if ( !attname.startsWith(NS_PREFIX_XMLNS)) { + String attPrefix = NS_PREFIX_DEFAULT; + String attLocalName = attname; + colon = attname.indexOf(':'); + if (colon >= 0) { + attPrefix = attname.substring(0, colon); + attLocalName = attname.substring(colon + 1); + } + + String attvalue = att.getValue(); + + // Get attribute's namespace + Namespace attNS = null; + String attURI = att.getNamespaceURI(); + if (attPrefix.isEmpty() && (attURI == null || NS_URI_DEFAULT.equals(attURI))) { + attNS = Namespace.NO_NAMESPACE; + } else { + // various conditions can lead here. + // the logical one is that we have a prefix for the + // attribute, and also a namespace URI. + // The alternative to that is in some conditions, + // the parser could have a 'default' or 'fixed' + // attribute that comes from an XSD used for + // validation. In that case there may not be a prefix + // There's also the possibility the DOM contains + // garbage. + if (attPrefix.length() > 0) { + // If the att has a prefix, we can assume that + // the DOM is valid, and we can just use the prefix. + // if this prefix conflicts with some other namespace + // then we re-declare it. If redeclaring it screws up + // other attributes in this Element, then the DOM + // was broken to start with. + if (attURI == null) { + // this can happen when the DOM is created + // without being namespace aware. we have a + // prefix, but the URI is not embedded in + // the Attribute itself. It must be declared + // on the element somewhere.... + // https://github.com/hunterhacker/jdom/issues/138 + attNS = element.getNamespace(attPrefix); + } else { + attNS = Namespace.getNamespace(attPrefix, attURI); + } + } else { + // OK, no prefix. + // must be a defaulted value from an XSD. + // perhaps we can find the namespace in our + // element's ancestry, and use the prefix from that. + HashMap tmpmap = new HashMap(); + for(Namespace nss : element.getNamespacesInScope()) { + if (nss.getPrefix().length() > 0 && nss.getURI().equals(attURI)) { + attNS = nss; + break; + } + tmpmap.put(nss.getPrefix(), nss); + } + if (attNS == null) { + // we cannot find a 'prevailing' namespace that has a prefix + // that is for this namespace. + // This basically means that there's an XMLSchema, for the + // DEFAULT namespace, and there's a defaulted/fixed + // attribute definition in the XMLSchema that's targeted + // for this namespace,... but, the user has either not + // declared a prefixed version of the namespace, or has + // re-declared the same prefix at a lower level with a + // different namespace. + // All of these things are possible. + // Create some sort of default prefix. + int cnt = 0; + String base = "attns"; + String pfx = base + cnt; + while (tmpmap.containsKey(pfx)) { + cnt++; + pfx = base + cnt; + } + attNS = Namespace.getNamespace(pfx, attURI); + } + } + } + + Attribute attribute = + factory.attribute(attLocalName, attvalue, attNS); + factory.setAttribute(element, attribute); + } + } + + // Recurse on child nodes + // The list should never be null nor should it ever contain + // null nodes, but some DOM impls are broken + org.w3c.dom.NodeList children = node.getChildNodes(); + if (children != null) { + int size = children.getLength(); + for (int i = 0; i < size; i++) { + org.w3c.dom.Node item = children.item(i); + if (item != null) { + buildTree(item, doc, element, false); + } + } + } + break; + + case org.w3c.dom.Node.TEXT_NODE: + factory.addContent(current, build((org.w3c.dom.Text)node)); + break; + + case org.w3c.dom.Node.CDATA_SECTION_NODE: + factory.addContent(current, build((org.w3c.dom.CDATASection)node)); + break; + + + case org.w3c.dom.Node.PROCESSING_INSTRUCTION_NODE: + if (atRoot) { + factory.addContent(doc, build((org.w3c.dom.ProcessingInstruction)node)); + } else { + factory.addContent(current, build((org.w3c.dom.ProcessingInstruction)node)); + } + break; + + case org.w3c.dom.Node.COMMENT_NODE: + if (atRoot) { + factory.addContent(doc, build((org.w3c.dom.Comment)node)); + } else { + factory.addContent(current, build((org.w3c.dom.Comment)node)); + } + break; + + case org.w3c.dom.Node.ENTITY_REFERENCE_NODE: + factory.addContent(current, build((org.w3c.dom.EntityReference)node)); + break; + + case org.w3c.dom.Node.ENTITY_NODE: + // ?? + break; + + case org.w3c.dom.Node.DOCUMENT_TYPE_NODE: + + factory.addContent(doc, build((org.w3c.dom.DocumentType)node)); + break; + } + } +} diff --git a/core/src/java/org/jdom/input/JDOMParseException.java b/core/src/java/org/jdom/input/JDOMParseException.java new file mode 100644 index 0000000..f57931c --- /dev/null +++ b/core/src/java/org/jdom/input/JDOMParseException.java @@ -0,0 +1,174 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input; + +import org.jdom.*; +import org.xml.sax.*; + +/** + * Thrown during parse errors, with information about where the parse error + * occurred as well as access to the partially built document. + * + * @author Laurent Bihanic + */ +public class JDOMParseException extends JDOMException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * The portion of the document that was successfully built before + * the parse error occurred. + */ + private final Document partialDocument; + + /** + * This will create a parse Exception with the given + * message and wrap the Exception that cause a document + * parse to fail. + * + * @param message String message indicating + * the problem that occurred. + * @param cause Throwable that caused this + * to be thrown. + */ + public JDOMParseException(String message, Throwable cause) { + this(message, cause, null); + } + + /** + * This will create a parse Exception with the given + * message and the partial document and wrap the + * Exception that cause a document parse to fail. + * + * @param message String message indicating + * the problem that occurred. + * @param cause Throwable that caused this + * to be thrown. + * @param partialDocument Document the portion of + * the input XML document that was + * successfully built. + */ + public JDOMParseException(String message, Throwable cause, + Document partialDocument) { + super(message, cause); + this.partialDocument = partialDocument; + } + + /** + * Returns the partial document that was successfully built before + * the error occurred. + * + * @return the partial document or null if none. + */ + public Document getPartialDocument() { + return partialDocument; + } + + /** + * Returns the public identifier of the entity where the + * parse error occurred. + * + * @return a string containing the public identifier, or + * null if the information is not available. + */ + public String getPublicId() { + return (getCause() instanceof SAXParseException)? + ((SAXParseException)getCause()).getPublicId(): null; + } + + /** + * Returns the system identifier of the entity where the + * parse error occurred. + * + * @return a string containing the system identifier, or + * null if the information is not available. + */ + public String getSystemId() { + return (getCause() instanceof SAXParseException)? + ((SAXParseException)getCause()).getSystemId(): null; + } + + /** + * Returns the line number of the end of the text where the + * parse error occurred. + *

+ * The first line in the document is line 1.

+ * + * @return an integer representing the line number, or -1 + * if the information is not available. + */ + public int getLineNumber() { + return (getCause() instanceof SAXParseException)? + ((SAXParseException)getCause()).getLineNumber(): -1; + } + + /** + * Returns the column number of the end of the text where the + * parse error occurred. + *

+ * The first column in a line is position 1.

+ * + * @return an integer representing the column number, or -1 + * if the information is not available. + */ + public int getColumnNumber() { + return (getCause() instanceof SAXParseException)? + ((SAXParseException)getCause()).getColumnNumber(): -1; + } +} + diff --git a/core/src/java/org/jdom/input/SAXBuilder.java b/core/src/java/org/jdom/input/SAXBuilder.java new file mode 100644 index 0000000..d69c26f --- /dev/null +++ b/core/src/java/org/jdom/input/SAXBuilder.java @@ -0,0 +1,1305 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input; + +import static org.jdom.JDOMConstants.SAX_FEATURE_EXTERNAL_ENT; +import static org.jdom.JDOMConstants.SAX_PROPERTY_DECLARATION_HANDLER; +import static org.jdom.JDOMConstants.SAX_PROPERTY_LEXICAL_HANDLER; +import static org.jdom.JDOMConstants.SAX_PROPERTY_LEXICAL_HANDLER_ALT; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLFilter; +import org.xml.sax.XMLReader; + +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.Verifier; +import org.jdom.input.sax.BuilderErrorHandler; +import org.jdom.input.sax.DefaultSAXHandlerFactory; +import org.jdom.input.sax.SAXBuilderEngine; +import org.jdom.input.sax.SAXEngine; +import org.jdom.input.sax.SAXHandler; +import org.jdom.input.sax.SAXHandlerFactory; +import org.jdom.input.sax.XMLReaderJDOMFactory; +import org.jdom.input.sax.XMLReaderSAX2Factory; +import org.jdom.input.sax.XMLReaders; + +/** + * Builds a JDOM Document using a SAX parser. + *

+ * SAXbuilder uses a third-party SAX parser (chosen by JAXP by default, or you + * can configure it manually) to handle the parsing duties and uses an instance + * of a SAXHandler to listen to the SAX events in order to construct a document + * with JDOM content using a JDOMFactory. Information about SAX can be found at + * http://www.saxproject.org. + *

+ * For a complete description of how SAXBuilder is used, and how to customise + * the process you should look at the {@link org.jdom.input.sax} package + * documentation. + *

+ * JDOM users needing to customise the SAX parsing process have traditionally + * sub-classed this SAXBuilder class. In JDOM2 this should never be necessary. + * Please read the full documentation of this class, {@link SAXHandler}, + * {@link SAXHandlerFactory}, {@link JDOMFactory}, and the package documentation + * for {@link org.jdom.input.sax} before overriding this class. Future versions + * of JDOM2 may make this class 'final'. I you feel you have a good reason to + * subclass SAXBuilder please mention it on jdom-interest mailing list + * so that SAXBuilder can be extended or adapted to handle your use-case. + *

+ * Neither SAXBuilder nor anything derived from SAXBuilder is thread-safe. You + * must ensure that SAXBuilder is used in a single thread, or that sufficient + * locking is in place to ensure that SAXBuilder is not concurrently accessed. + * See the special note on {@link #buildEngine()}. + *

+ * Known issues: + *

    + *
  • Relative paths for a {@link DocType} or {@link EntityRef} may be + * converted by the SAX parser into absolute paths. + *
  • SAX does not recognise whitespace character content outside the root + * element (nor does JDOM) so any formatting outside the root Element will be + * lost. + *
+ * + * @see org.jdom.input.sax + * @author Jason Hunter + * @author Brett McLaughlin + * @author Dan Schaffer + * @author Philip Nelson + * @author Alex Rosen + * @author Rolf Lear + */ +public class SAXBuilder implements SAXEngine { + + /** Default source of SAXHandlers */ + private static final SAXHandlerFactory DEFAULTSAXHANDLERFAC = + new DefaultSAXHandlerFactory(); + + /** Default source of JDOM Content */ + private static final JDOMFactory DEFAULTJDOMFAC = new DefaultJDOMFactory(); + + /* + * ==================================================================== + */ + + /** + * The XMLReader pillar of SAXBuilder + */ + private XMLReaderJDOMFactory readerfac = null; + + /** + * The SAXHandler pillar of SAXBuilder + */ + private SAXHandlerFactory handlerfac = null; + + /** + * The JDOMFactory pillar for creating new JDOM objects + */ + private JDOMFactory jdomfac = null; + + /* + * ======================================================================== + * Configuration settings for SAX parsing. + * ======================================================================== + */ + + /** User-specified features to be set on the SAX parser */ + private final HashMap features = new HashMap(5); + + /** User-specified properties to be set on the SAX parser */ + private final HashMap properties = new HashMap(5); + + /** ErrorHandler class to use */ + private ErrorHandler saxErrorHandler = null; + + /** EntityResolver class to use */ + private EntityResolver saxEntityResolver = null; + + /** DTDHandler class to use */ + private DTDHandler saxDTDHandler = null; + + /** XMLFilter instance to use */ + private XMLFilter saxXMLFilter = null; + + /** Whether expansion of entities should occur */ + private boolean expand = true; + + /** Whether to ignore ignorable whitespace */ + private boolean ignoringWhite = false; + + /** Whether to ignore all whitespace content */ + private boolean ignoringBoundaryWhite = false; + + /** Whether parser reuse is allowed. */ + private boolean reuseParser = true; + + /** The current SAX parser, if parser reuse has been activated. */ + private SAXEngine engine = null; + + /** + * Creates a new JAXP-based SAXBuilder. The underlying parser will not + * validate. + * + * @see SAXBuilder#SAXBuilder(XMLReaderJDOMFactory, SAXHandlerFactory, + * JDOMFactory) + * @see XMLReaders#NONVALIDATING + * @see DefaultSAXHandlerFactory + * @see DefaultJDOMFactory + */ + public SAXBuilder() { + this(null, null, null); + } + + /** + * Creates a new JAXP-based SAXBuilder. The underlying parser will validate + * (using DTD) or not according to the given parameter. If you want Schema + * validation then use SAXBuilder(XMLReaders.XSDVALIDATOR) + * + * @see SAXBuilder#SAXBuilder(XMLReaderJDOMFactory, SAXHandlerFactory, + * JDOMFactory) + * @see XMLReaders#NONVALIDATING + * @see XMLReaders#DTDVALIDATING + * @see DefaultSAXHandlerFactory + * @see DefaultJDOMFactory + * @see org.jdom.input.sax for important details on SAXBuilder + * @param validate + * boolean indicating if DTD validation should occur. + * @deprecated use {@link SAXBuilder#SAXBuilder(XMLReaderJDOMFactory)} with + * either {@link XMLReaders#DTDVALIDATING} + * or {@link XMLReaders#NONVALIDATING} + */ + @Deprecated + public SAXBuilder(final boolean validate) { + this(validate + ? XMLReaders.DTDVALIDATING + : XMLReaders.NONVALIDATING, + null, null); + } + + /** + * Creates a new SAXBuilder using the specified SAX parser. The underlying + * parser will not validate. + * + * @see SAXBuilder#SAXBuilder(XMLReaderJDOMFactory, SAXHandlerFactory, + * JDOMFactory) + * @see XMLReaderSAX2Factory + * @see DefaultSAXHandlerFactory + * @see DefaultJDOMFactory + * @see org.jdom.input.sax for important details on SAXBuilder + * @param saxDriverClass + * String name of SAX Driver to use for parsing. + * @deprecated use {@link SAXBuilder#SAXBuilder(XMLReaderJDOMFactory)} with + * {@link XMLReaderSAX2Factory#XMLReaderSAX2Factory(boolean, String)} + */ + @Deprecated + public SAXBuilder(final String saxDriverClass) { + this(saxDriverClass, false); + } + + /** + * Creates a new SAXBuilder using the specified SAX2.0 parser source. The + * underlying parser will validate or not according to the given parameter. + * + * @see SAXBuilder#SAXBuilder(XMLReaderJDOMFactory, SAXHandlerFactory, + * JDOMFactory) + * @see XMLReaderSAX2Factory + * @see DefaultSAXHandlerFactory + * @see DefaultJDOMFactory + * @see org.jdom.input.sax for important details on SAXBuilder + * @param saxDriverClass + * String name of SAX Driver to use for parsing. + * @param validate + * boolean indicating if validation should occur. + * @deprecated use {@link SAXBuilder#SAXBuilder(XMLReaderJDOMFactory)} with + * {@link XMLReaderSAX2Factory#XMLReaderSAX2Factory(boolean, String)} + */ + @Deprecated + public SAXBuilder(final String saxDriverClass, final boolean validate) { + this(new XMLReaderSAX2Factory(validate, saxDriverClass), null, null); + } + + /** + * Creates a new SAXBuilder with the specified XMLReaderJDOMFactory. + *

+ * + * @see SAXBuilder#SAXBuilder(XMLReaderJDOMFactory, SAXHandlerFactory, + * JDOMFactory) + * @see XMLReaderJDOMFactory + * @see XMLReaders#NONVALIDATING + * @see DefaultSAXHandlerFactory + * @see DefaultJDOMFactory + * @see org.jdom.input.sax for important details on SAXBuilder + * @param readersouce + * the {@link XMLReaderJDOMFactory} that supplies XMLReaders. If the + * value is null then a Non-Validating JAXP-based SAX2.0 parser will + * be used. + */ + public SAXBuilder(final XMLReaderJDOMFactory readersouce) { + this(readersouce, null, null); + } + + /** + * Creates a new SAXBuilder. This is the base constructor for all other + * SAXBuilder constructors: they all find a way to create a + * JDOMXMLReaderFactory and then call this constructor with that factory, + * and the {@link DefaultSAXHandlerFactory} and {@link DefaultJDOMFactory}. + *

+ * + * @see XMLReaderJDOMFactory + * @see XMLReaders#NONVALIDATING + * @see SAXHandlerFactory + * @see DefaultSAXHandlerFactory + * @see JDOMFactory + * @see DefaultJDOMFactory + * @see org.jdom.input.sax for important details on SAXBuilder + * @param xmlreaderfactory + * a {@link XMLReaderJDOMFactory} that creates XMLReaders. Specify + * null for the default. + * @param handlerfactory + * a {@link SAXHandlerFactory} that creates SAXHandlers Specify null + * for the default. + * @param jdomfactory + * a {@link JDOMFactory} that creates JDOM Content. Specify null for + * the default. + */ + public SAXBuilder(final XMLReaderJDOMFactory xmlreaderfactory, final SAXHandlerFactory handlerfactory, + final JDOMFactory jdomfactory) { + this.readerfac = xmlreaderfactory == null + ? XMLReaders.NONVALIDATING + : xmlreaderfactory; + this.handlerfac = handlerfactory == null + ? DEFAULTSAXHANDLERFAC + : handlerfactory; + this.jdomfac = jdomfactory == null + ? DEFAULTJDOMFAC + : jdomfactory; + } + + /** + * Returns the driver class assigned in the constructor, or null if none. + * The driver class is only available if a SAX2 source was specified. This + * method is available for backward-compatibility with JDOM 1.x + * + * @return the driver class assigned in the constructor + * @deprecated as the driver class is only available in limited situations + * and anyway it had to be supplied in a constructor as either a + * direct value or as an {@link XMLReaderSAX2Factory} instance. + */ + @Deprecated + public String getDriverClass() { + if (readerfac instanceof XMLReaderSAX2Factory) { + return ((XMLReaderSAX2Factory) readerfac).getDriverClassName(); + } + return null; + } + + /** + * Returns the current {@link org.jdom.JDOMFactory} in use. + * + * @return the factory in use + * @deprecated as it is replaced by {@link #getJDOMFactory()} + */ + @Deprecated + public JDOMFactory getFactory() { + return getJDOMFactory(); + } + + /** + * Returns the current {@link org.jdom.JDOMFactory} in use. + * + * @return the factory in use + */ + @Override + public JDOMFactory getJDOMFactory() { + return jdomfac; + } + + /** + * This sets a custom JDOMFactory for the builder. Use this to build the + * tree with your own subclasses of the JDOM classes. + * + * @param factory + * JDOMFactory to use + * @deprecated as it is replaced by {@link #setJDOMFactory(JDOMFactory)} + */ + @Deprecated + public void setFactory(final JDOMFactory factory) { + setJDOMFactory(factory); + } + + /** + * This sets a custom JDOMFactory for the builder. Use this to build the + * tree with your own subclasses of the JDOM classes. + * + * @param factory + * JDOMFactory to use + */ + public void setJDOMFactory(final JDOMFactory factory) { + this.jdomfac = factory; + engine = null; + } + + /** + * Get the current XMLReader factory. + * + * @return the current JDOMXMLReaderFactory + */ + public XMLReaderJDOMFactory getXMLReaderFactory() { + return readerfac; + } + + /** + * Set the current XMLReader factory. + * + * @param rfac + * the JDOMXMLReaderFactory to set. A null rfac will indicate the + * default {@link XMLReaders#NONVALIDATING} + */ + public void setXMLReaderFactory(final XMLReaderJDOMFactory rfac) { + readerfac = rfac == null + ? XMLReaders.NONVALIDATING + : rfac; + engine = null; + } + + /** + * Get the SAXHandlerFactory used to supply SAXHandlers to this SAXBuilder. + * + * @return the current SAXHandlerFactory (never null). + */ + public SAXHandlerFactory getSAXHandlerFactory() { + return handlerfac; + } + + /** + * Set the SAXHandlerFactory to be used by this SAXBuilder. + * + * @param factory + * the required SAXHandlerFactory. A null input factory will request + * the {@link DefaultSAXHandlerFactory}. + */ + public void setSAXHandlerFactory(final SAXHandlerFactory factory) { + this.handlerfac = factory == null ? DEFAULTSAXHANDLERFAC : factory; + engine = null; + } + + /** + * Returns whether validation is to be performed during the build. + * + * @return whether validation is to be performed during the build + * @deprecated in lieu of {@link #isValidating()} + */ + @Deprecated + public boolean getValidation() { + return isValidating(); + } + + /** + * Returns whether validation is to be performed during the build. + * + * @return whether validation is to be performed during the build + */ + @Override + public boolean isValidating() { + return readerfac.isValidating(); + } + + /** + * This sets validation for the builder. + *

+ * Do Not Use + *

+ * JDOM2 introduces the concept of XMLReader factories. The XMLReader is + * what determines the type of validation. A simple boolean is not enough to + * indicate what sort of validation is required. The + * {@link #setXMLReaderFactory(XMLReaderJDOMFactory)} method provides a + * means to be more specific about validation. + *

+ * For backward compatibility this method has been retained, but its use is + * discouraged. It does make some logical choices though. The code is + * equivalent to: + *

+ * + *

+	 * setXMLReaderFactory(XMLReaders.DTDVALIDATING)
+	 * 
+ * + * for true, and + * + *
+	 * setXMLReaderFactory(XMLReaders.NONVALIDATING)
+	 * 
+ * + * for false. + * + * @see #setXMLReaderFactory(XMLReaderJDOMFactory) + * @see XMLReaders#NONVALIDATING + * @see XMLReaders#DTDVALIDATING + * @param validate + * boolean indicating whether validation should occur. + * @deprecated use {@link #setXMLReaderFactory(XMLReaderJDOMFactory)} + */ + @Deprecated + public void setValidation(final boolean validate) { + setXMLReaderFactory(validate + ? XMLReaders.DTDVALIDATING + : XMLReaders.NONVALIDATING); + } + + /** + * Returns the {@link ErrorHandler} assigned, or null if none. When the + * SAXBuilder parses a document it will always have an ErrorHandler but it + * will be an instance of {@link BuilderErrorHandler} unless you specify a + * different ErrorHandler in {@link #setErrorHandler(ErrorHandler)}. In + * other words, a null return value from here indicates a default will be + * used. + * + * @return the ErrorHandler assigned, or null if SAXBuilder will create a + * default ErrorHandler when needed. + */ + @Override + public ErrorHandler getErrorHandler() { + return saxErrorHandler; + } + + /** + * This sets custom ErrorHandler for the Builder. Setting a null value will + * indicate SAXBuilder should create a default ErrorHandler when needed. + * + * @param errorHandler + * ErrorHandler + */ + public void setErrorHandler(final ErrorHandler errorHandler) { + saxErrorHandler = errorHandler; + engine = null; + } + + /** + * Returns the {@link EntityResolver} assigned, or null if none. + * + * @return the EntityResolver assigned + */ + @Override + public EntityResolver getEntityResolver() { + return saxEntityResolver; + } + + /** + * This sets custom EntityResolver for the Builder. + * + * @param entityResolver + * EntityResolver + */ + public void setEntityResolver(final EntityResolver entityResolver) { + saxEntityResolver = entityResolver; + engine = null; + } + + /** + * Returns the {@link DTDHandler} assigned, or null if the assigned + * {@link SAXHandler} will be used for DTD SAX events. + * + * @return the DTDHandler assigned + */ + @Override + public DTDHandler getDTDHandler() { + return saxDTDHandler; + } + + /** + * This sets custom DTDHandler for the Builder. Setting a null + * value indicates that SAXBuilder should use the assigned SAXHandler for + * DTD processing. + * + * @param dtdHandler + * DTDHandler + */ + public void setDTDHandler(final DTDHandler dtdHandler) { + saxDTDHandler = dtdHandler; + engine = null; + } + + /** + * Returns the {@link XMLFilter} used during parsing, or null if none. + * + * @return the XMLFilter used during parsing + */ + public XMLFilter getXMLFilter() { + return saxXMLFilter; + } + + /** + * This sets a custom {@link org.xml.sax.XMLFilter} for the builder. + *

+ * Care should be taken to ensure that the specified xmlFilter is reentrant + * and thread-safe. + *

+ * SAXBuilder will set this instance as the parent instance for all + * XMLReaders that may be created, and these may (depending on SAXBuilder + * usage) be accessed concurrently. It is the responsibility of the JDOM + * user to ensure that if the XMLFilter is not thread-safe then neither the + * SAXBuilder nor any of it's SAXEngines are accessed concurrently. + * + * @param xmlFilter + * the XMLFilter to use + */ + public void setXMLFilter(final XMLFilter xmlFilter) { + saxXMLFilter = xmlFilter; + engine = null; + } + + /** + * Returns whether element content whitespace is to be ignored during the + * build. + * + * @return whether element content whitespace is to be ignored during the + * build + */ + @Override + public boolean getIgnoringElementContentWhitespace() { + return ignoringWhite; + } + + /** + * Specifies whether or not the parser should eliminate whitespace in + * element content (sometimes known as "ignorable whitespace") when building + * the document. Only whitespace which is contained within element content + * that has an element only content model will be eliminated (see XML Rec + * 3.2.1). For this setting to take effect requires that validation be + * turned on. The default value of this setting is false. + * + * @param ignoringWhite + * Whether to ignore ignorable whitespace + */ + public void setIgnoringElementContentWhitespace(final boolean ignoringWhite) { + this.ignoringWhite = ignoringWhite; + engine = null; + } + + /** + * Returns whether or not the parser will eliminate element content + * containing only whitespace. + * + * @return boolean - whether only whitespace content will be + * ignored during build. + * @see #setIgnoringBoundaryWhitespace + */ + @Override + public boolean getIgnoringBoundaryWhitespace() { + return ignoringBoundaryWhite; + } + + /** + * Specifies whether or not the parser should elminate boundary whitespace, + * a term that indicates whitespace-only text between element tags. This + * feature is a lot like + * {@link #setIgnoringElementContentWhitespace(boolean)} but this feature is + * more aggressive and doesn't require validation be turned on. The + * {@link #setIgnoringElementContentWhitespace(boolean)} call impacts the + * SAX parse process while this method impacts the JDOM build process, so it + * can be beneficial to turn both on for efficiency. For implementation + * efficiency, this method actually removes all whitespace-only text() + * nodes. That can, in some cases (like between an element tag and a + * comment) include whitespace that isn't just boundary whitespace. The + * default is false. + * + * @param ignoringBoundaryWhite + * Whether to ignore whitespace-only text nodes + */ + public void setIgnoringBoundaryWhitespace(final boolean ignoringBoundaryWhite) { + this.ignoringBoundaryWhite = ignoringBoundaryWhite; + engine = null; + } + + /** + * Returns whether or not entities are being expanded into normal text + * content. + * + * @return whether entities are being expanded + */ + @Override + public boolean getExpandEntities() { + return expand; + } + + /** + *

+ * This sets whether or not to expand entities for the builder. A true means + * to expand entities as normal content. A false means to leave entities + * unexpanded as EntityRef objects. The default is true. + *

+ *

+ * When this setting is false, the internal DTD subset is retained; when + * this setting is true, the internal DTD subset is not retained. + *

+ *

+ * Note that Xerces (at least up to 1.4.4) has a bug where entities in + * attribute values will be incorrectly reported if this flag is turned off, + * resulting in entities appearing within element content. When turning + * entity expansion off either avoid entities in attribute values, or use + * another parser like Crimson. + * http://nagoya.apache.org/bugzilla/show_bug.cgi?id=6111 + *

+ * + * @param expand + * boolean indicating whether entity expansion should + * occur. + */ + public void setExpandEntities(final boolean expand) { + this.expand = expand; + engine = null; + } + + /** + * Returns whether the contained SAX parser instance is reused across + * multiple parses. The default is true. + * + * @return whether the contained SAX parser instance is reused across + * multiple parses + */ + public boolean getReuseParser() { + return reuseParser; + } + + /** + * Specifies whether this builder will reuse the same SAX parser when + * performing subsequent parses or allocate a new parser for each parse. The + * default value of this setting is true (parser reuse). + *

+ * Note: SAX parser instances are not thread safe (they are + * not even reentrant), and nor are SAXBuilder instances. Setting parser + * reuse does not imply the parser is thread-safe. + *

+ * + * @param reuseParser + * Whether to reuse the SAX parser. + */ + public void setReuseParser(final boolean reuseParser) { + this.reuseParser = reuseParser; + if (!reuseParser) { + engine = null; + } + } + + /** + * Specifies whether this builder will do fast reconfiguration of the + * underlying SAX parser when reuseParser is true. This improves performance + * in cases where SAXBuilders are reused and lots of small documents are + * frequently parsed. This avoids attempting to set features on the SAX + * parser each time build() is called which result in + * SaxNotRecognizedExceptions. This should ONLY be set for builders where + * this specific case is an issue. The default value of this setting is + * false (no fast reconfiguration). If reuseParser is false, + * calling this has no effect. + * + * @param fastReconfigure + * Whether to do a fast reconfiguration of the parser + * @deprecated All reused Parsers are now fast-reconfigured. No need to set + * it. + */ + @Deprecated + public void setFastReconfigure(final boolean fastReconfigure) { + // do nothing + } + + /** + * This sets a feature on the SAX parser. See the SAX documentation for + * more information.

+ *

+ * NOTE: SAXBuilder requires that some particular features of the SAX parser + * be set up in certain ways for it to work properly. The list of such + * features may change in the future. Therefore, the use of this method may + * cause parsing to break, and even if it doesn't break anything today it + * might break parsing in a future JDOM version, because what JDOM parsers + * require may change over time. Use with caution. + *

+ * JDOM uses {@link XMLReaderJDOMFactory} instances to provide XMLReader + * instances. If you require special configuration on your XMLReader you + * should consider extending or implementing an XMLReaderJDOMFactory in the + * {@link org.jdom.input.sax} package. + * + * @param name + * The feature name, which is a fully-qualified URI. + * @param value + * The requested state of the feature (true or false). + */ + public void setFeature(final String name, final boolean value) { + // Save the specified feature for later. + features.put(name, value ? Boolean.TRUE : Boolean.FALSE); + engine = null; + } + + /** + * This sets a property on the SAX parser. See the SAX documentation for + * more information. + *

+ * NOTE: SAXBuilder requires that some particular properties of the SAX + * parser be set up in certain ways for it to work properly. The list of + * such properties may change in the future. Therefore, the use of this + * method may cause parsing to break, and even if it doesn't break anything + * today it might break parsing in a future JDOM version, because what JDOM + * parsers require may change over time. Use with caution. + *

+ * JDOM uses {@link XMLReaderJDOMFactory} instances to provide XMLReader + * instances. If you require special configuration on your XMLReader you + * should consider extending or implementing an XMLReaderJDOMFactory in the + * {@link org.jdom.input.sax} package. + * + * @param name + * The property name, which is a fully-qualified URI. + * @param value + * The requested value for the property. + */ + public void setProperty(final String name, final Object value) { + // Save the specified property for later. + properties.put(name, value); + engine = null; + } + + /** + * This method builds a new and reusable {@link SAXEngine}. + * Each time this method is called a new instance of a SAXEngine will be + * returned. + *

+ * This method is used internally by the various SAXBuilder.build(*) methods + * (if any configuration has changed) but can also be used as a mechanism + * for creating SAXEngines to be used in parsing pools or other optimised + * structures. + * + * @return a {@link SAXEngine} representing the current state of the + * current SAXBuilder settings. + * @throws JDOMException + * if there is any problem initialising the engine. + */ + public SAXEngine buildEngine() throws JDOMException { + + // Create and configure the content handler. + final SAXHandler contentHandler = handlerfac.createSAXHandler(jdomfac); + + contentHandler.setExpandEntities(expand); + contentHandler.setIgnoringElementContentWhitespace(ignoringWhite); + contentHandler.setIgnoringBoundaryWhitespace(ignoringBoundaryWhite); + + final XMLReader parser = createParser(); + // Configure parser + configureParser(parser, contentHandler); + final boolean valid = readerfac.isValidating(); + + return new SAXBuilderEngine(parser, contentHandler, valid); + } + + /** + * Allow overriding classes access to the Parser before it is used in a + * SAXBuilderEngine. + * + * @return a XMLReader parser. + * @throws JDOMException + * if there is a problem + */ + protected XMLReader createParser() throws JDOMException { + XMLReader parser = readerfac.createXMLReader(); + + // Install optional filter + if (saxXMLFilter != null) { + // Connect filter chain to parser + XMLFilter root = saxXMLFilter; + while (root.getParent() instanceof XMLFilter) { + root = (XMLFilter) root.getParent(); + } + root.setParent(parser); + + // Read from filter + parser = saxXMLFilter; + } + + return parser; + } + + /** + * This method retrieves (or builds) a SAXBuilderEngine that represents the + * current SAXBuilder state. + * + * @return a {@link SAXBuilderEngine} representing the current state of the + * current SAXBuilder settings. + * @throws JDOMException + * if there is any problem initializing the engine. + */ + private SAXEngine getEngine() throws JDOMException { + + if (engine != null) { + return engine; + } + + engine = buildEngine(); + return engine; + } + + /** + * This configures the XMLReader to be used for reading the XML document. + *

+ * The default implementation sets various options on the given XMLReader, + * such as validation, DTD resolution, entity handlers, etc., according to + * the options that were set (e.g. via setEntityResolver) and + * set various SAX properties and features that are required for JDOM + * internals. These features may change in future releases, so change this + * behavior at your own risk. + *

+ * + * @param parser + * the XMLReader to configure. + * @param contentHandler + * The SAXHandler to use for the XMLReader + * @throws JDOMException + * if configuration fails. + */ + protected void configureParser(final XMLReader parser, final SAXHandler contentHandler) + throws JDOMException { + + // Setup SAX handlers. + + parser.setContentHandler(contentHandler); + + if (saxEntityResolver != null) { + parser.setEntityResolver(saxEntityResolver); + } + + if (saxDTDHandler != null) { + parser.setDTDHandler(saxDTDHandler); + } else { + parser.setDTDHandler(contentHandler); + } + + if (saxErrorHandler != null) { + parser.setErrorHandler(saxErrorHandler); + } else { + parser.setErrorHandler(new BuilderErrorHandler()); + } + + boolean success = false; + + try { + parser.setProperty(SAX_PROPERTY_LEXICAL_HANDLER, + contentHandler); + success = true; + } catch (final SAXNotSupportedException e) { + // No lexical reporting available + } catch (final SAXNotRecognizedException e) { + // No lexical reporting available + } + + // Some parsers use alternate property for lexical handling (grr...) + if (!success) { + try { + parser.setProperty(SAX_PROPERTY_LEXICAL_HANDLER_ALT, + contentHandler); + success = true; + } catch (final SAXNotSupportedException e) { + // No lexical reporting available + } catch (final SAXNotRecognizedException e) { + // No lexical reporting available + } + } + + // Set any user-specified features on the parser. + for (final Map.Entry me : features.entrySet()) { + internalSetFeature(parser, me.getKey(), me.getValue().booleanValue(), me.getKey()); + } + + // Set any user-specified properties on the parser. + for (final Map.Entry me : properties.entrySet()) { + internalSetProperty(parser, me.getKey(), me.getValue(), me.getKey()); + } + + // Set entity expansion + // Note SAXHandler can work regardless of how this is set, but when + // entity expansion it's worth it to try to tell the parser not to + // even bother with external general entities. + // Apparently no parsers yet support this feature. + // XXX It might make sense to setEntityResolver() with a resolver + // that simply ignores external general entities + try { + if (parser.getFeature(SAX_FEATURE_EXTERNAL_ENT) != expand) { + parser.setFeature(SAX_FEATURE_EXTERNAL_ENT, expand); + } + } catch (final SAXException e) { /* Ignore... */ + } + + // Try setting the DeclHandler if entity expansion is off + if (!expand) { + try { + parser.setProperty(SAX_PROPERTY_DECLARATION_HANDLER, + contentHandler); + success = true; + } catch (final SAXNotSupportedException e) { + // No lexical reporting available + } catch (final SAXNotRecognizedException e) { + // No lexical reporting available + } + } + + } + + /** + * Tries to set a feature on the parser. If the feature cannot be set, + * throws a JDOMException describing the problem. + */ + private void internalSetFeature(final XMLReader parser, final String feature, + final boolean value, final String displayName) throws JDOMException { + try { + parser.setFeature(feature, value); + } catch (final SAXNotSupportedException e) { + throw new JDOMException( + displayName + " feature not supported for SAX driver " + parser.getClass().getName()); + } catch (final SAXNotRecognizedException e) { + throw new JDOMException( + displayName + " feature not recognized for SAX driver " + parser.getClass().getName()); + } + } + + /** + *

+ * Tries to set a property on the parser. If the property cannot be set, + * throws a JDOMException describing the problem. + *

+ */ + private void internalSetProperty(final XMLReader parser, final String property, + final Object value, final String displayName) throws JDOMException { + try { + parser.setProperty(property, value); + } catch (final SAXNotSupportedException e) { + throw new JDOMException( + displayName + " property not supported for SAX driver " + parser.getClass().getName()); + } catch (final SAXNotRecognizedException e) { + throw new JDOMException( + displayName + " property not recognized for SAX driver " + parser.getClass().getName()); + } + } + + /** + * This builds a document from the supplied input source. + * + * @param in + * InputSource to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + @Override + public Document build(final InputSource in) + throws JDOMException, IOException { + + try { + return getEngine().build(in); + } finally { + if (!reuseParser) { + engine = null; + } + } + + } + + /** + *

+ * This builds a document from the supplied input stream. + *

+ * + * @param in + * InputStream to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed. + */ + @Override + public Document build(final InputStream in) + throws JDOMException, IOException { + try { + return getEngine().build(in); + } finally { + if (!reuseParser) { + engine = null; + } + } + } + + /** + *

+ * This builds a document from the supplied filename. + *

+ * + * @param file + * File to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + @Override + public Document build(final File file) + throws JDOMException, IOException { + try { + return getEngine().build(file); + } finally { + if (!reuseParser) { + engine = null; + } + } + } + + /** + *

+ * This builds a document from the supplied URL. + *

+ * + * @param url + * URL to read from. + * @return Document - resultant Document object. + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed. + */ + @Override + public Document build(final URL url) + throws JDOMException, IOException { + try { + return getEngine().build(url); + } finally { + if (!reuseParser) { + engine = null; + } + } + } + + /** + *

+ * This builds a document from the supplied input stream. + *

+ * + * @param in + * InputStream to read from. + * @param systemId + * base for resolving relative URIs + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + @Override + public Document build(final InputStream in, final String systemId) + throws JDOMException, IOException { + try { + return getEngine().build(in, systemId); + } finally { + if (!reuseParser) { + engine = null; + } + } + } + + /** + *

+ * This builds a document from the supplied Reader. It's the programmer's + * responsibility to make sure the reader matches the encoding of the file. + * It's often easier and safer to use an InputStream rather than a Reader, + * and to let the parser auto-detect the encoding from the XML declaration. + *

+ * + * @param characterStream + * Reader to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + @Override + public Document build(final Reader characterStream) + throws JDOMException, IOException { + try { + return getEngine().build(characterStream); + } finally { + if (!reuseParser) { + engine = null; + } + } + } + + /** + *

+ * This builds a document from the supplied Reader. It's the programmer's + * responsibility to make sure the reader matches the encoding of the file. + * It's often easier and safer to use an InputStream rather than a Reader, + * and to let the parser auto-detect the encoding from the XML declaration. + *

+ * + * @param characterStream + * Reader to read from. + * @param systemId + * base for resolving relative URIs + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + @Override + public Document build(final Reader characterStream, final String systemId) + throws JDOMException, IOException { + + try { + return getEngine().build(characterStream, systemId); + } finally { + if (!reuseParser) { + engine = null; + } + } + } + + /** + *

+ * This builds a document from the supplied URI. The URI is typically a file name, or a URL. + * Do not use this method for parsing XML content that is in a Java String variable. + *

+ *

    + *
  • Right: ....build("path/to/file.xml"); + *
  • Right: ....build("http://my.example.com/xmlfile"); + *
  • Wrong: ....build("<root>data</root>"); + *
+ *

+ * If your XML content is in a Java String variable and you want to parse it, then use:
+ * ....build(new StringReader("<root>data</root>")); + *

+ * + * @param systemId + * URI for the input + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + @Override + public Document build(final String systemId) + throws JDOMException, IOException { + if (systemId == null) { + throw new NullPointerException( + "Unable to build a URI from a null systemID."); + } + try { + return getEngine().build(systemId); + } catch (IOException ioe) { + // OK, Issue #63 + // it is common for people to pass in raw XML content instead of + // a SystemID. To make troubleshooting easier, we do a simple check + // all valid XML documents start with a '<' character. + // no URI ever has an '<' character. + // if we think an XML document was passed in, we wrap the exception + // Typically this problem causes a MalformedURLException to be + // thrown, but that is not particular specified that way. Of + // interest, depending on the broken systemID, you could get a + // FileNotFoundException which is also an IOException, which will + // also be processed by this handler.... + + final int len = systemId.length(); + int i = 0; + while (i < len && Verifier.isXMLWhitespace(systemId.charAt(i))) { + i++; + } + if (i < len && '<' == systemId.charAt(i)) { + // our systemID URI has a '<' - this is likely the problem. + MalformedURLException mx = new MalformedURLException( + "SAXBuilder.build(String) expects the String to be " + + "a systemID, but in this instance it appears to be " + + "actual XML data."); + // include the original problem for accountability, and perhaps + // a false positive.... though very unlikely + mx.initCause(ioe); + throw mx; + } + // it is likely not an XML document - re-throw the exception + throw ioe; + } finally { + if (!reuseParser) { + engine = null; + } + } + } + +} diff --git a/core/src/java/org/jdom/input/StAXEventBuilder.java b/core/src/java/org/jdom/input/StAXEventBuilder.java new file mode 100644 index 0000000..9f4c6d5 --- /dev/null +++ b/core/src/java/org/jdom/input/StAXEventBuilder.java @@ -0,0 +1,283 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input; + +import java.util.Iterator; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.jdom.AttributeType; +import org.jdom.Comment; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.input.stax.DTDParser; + +/** + * Builds a JDOM Document from a StAX-based XMLEventReader. + *

+ * XMLEventReaders are pre-configured and as a result JDOM is not able to + * alter whether the input is validated, or whether the Events have escaped + * entities or not. These (and other) characteristics are configurable by + * setting the correct features and properties on the XMLInputFactory when it + * is used to create the XMLStreamReader. + *

+ * Useful configuration to set, or know about is: + *

    + *
  • StAX Events seldom differentiate between Text and CDATA content. You + * will likely want to configure your StAX factory (XMLInputFactory) with + * http://java.sun.com/xml/stream/properties/report-cdata-event + * for the default Java StAX implementation, or the equivalent property for your + * StAX engine. + *
  • The remaining XMLInputFactory settings are likely to work fine at their + * default values. + *
  • StAX is not likely to be your best option if you want a validating + * parser, at least not with the default (built-in Java implementation in Java6 + * which does not support it). Consider a SAX parser. + *
+ *

+ * From a JDOM perspective XMLStreamReaders are more efficient than + * XMLEventReaders. Where possible use an XMLStreamReader. + *

+ * If you happen to be looking at the source code, pay careful attention to the + * imports so you know what type of instance is being processed, whether it is + * a StAX class, or a JDOM class, because there are name conflicts. + * + * @author Rolf Lear + * + */ +public class StAXEventBuilder { + + + /** + * Create a Document from an XMLEventReader + * @param factory the {@link JDOMFactory} to use + * @param stream the XMLEventReader to read from + * @return the parsed Document + * @throws JDOMException if there is any issue + * (XMLStreamExceptions are wrapped). + */ + private static final Document process(final JDOMFactory factory, + final XMLEventReader events) throws JDOMException { + try { + + final Document document = factory.document(null); + Element current = null; + + XMLEvent event = events.peek(); + + if (XMLStreamConstants.START_DOCUMENT != event.getEventType()) { + throw new JDOMException("JDOM requires that XMLStreamReaders " + + "are at their beginning when being processed."); + } + + + + while (event.getEventType() != XMLStreamConstants.END_DOCUMENT) { + if (event.isStartDocument()) { + document.setBaseURI(event.getLocation().getSystemId()); + document.setProperty("ENCODING_SCHEME", + ((javax.xml.stream.events.StartDocument)event).getCharacterEncodingScheme()); + document.setProperty("STANDALONE", String.valueOf( + ((javax.xml.stream.events.StartDocument)event).isStandalone())); + // document.setProperty("ENCODING", + // ((StartDocument)event).getEncoding()); + } else if (event instanceof javax.xml.stream.events.DTD) { + //List list = (List)reader.getProperty("javax.xml.stream.entities"); + //System.out.println(list); + final DocType dtype = DTDParser.parse(((javax.xml.stream.events.DTD)event).getDocumentTypeDeclaration(), factory); + document.setDocType(dtype); + } else if (event.isStartElement()) { + final Element emt = processElement(factory, event.asStartElement()); + if (current == null) { + document.setRootElement(emt); + final DocType dt = document.getDocType(); + if (dt != null) { + dt.setElementName(emt.getName()); + } + } else { + current.addContent(emt); + } + current = emt; + } else if (event.isCharacters() && current != null) { + // ignore any character-based content (should only be spaces) + // outside of the root element. + final Characters chars = event.asCharacters(); + if (chars.isCData()) { + current.addContent(factory.cdata( + ((Characters)event).getData())); + } else { + current.addContent(factory.text( + ((Characters)event).getData())); + } + } else if (event instanceof javax.xml.stream.events.Comment) { + final Comment comment = factory.comment( + ((javax.xml.stream.events.Comment)event).getText()); + if (current == null) { + document.addContent(comment); + } else { + current.addContent(comment); + } + } else if (event.isEntityReference()) { + current.addContent(factory.entityRef( + ((javax.xml.stream.events.EntityReference)event).getName())); + } else if (event.isProcessingInstruction()) { + final ProcessingInstruction pi = factory.processingInstruction( + ((javax.xml.stream.events.ProcessingInstruction)event).getTarget(), + ((javax.xml.stream.events.ProcessingInstruction)event).getData()); + if (current == null) { + document.addContent(pi); + } else { + current.addContent(pi); + } + } else if (event.isEndElement()) { + current = current.getParentElement(); + } + if (events.hasNext()) { + event = events.nextEvent(); + } else { + break; + } + } + return document; + } catch (final XMLStreamException xse) { + throw new JDOMException("Unable to process XMLStream. See Cause.", xse); + } + } + + private static final Element processElement(final JDOMFactory factory, + final StartElement event) { + final QName qname = event.getName(); + + final Element element = factory.element(qname.getLocalPart(), + Namespace.getNamespace(qname.getPrefix(), qname.getNamespaceURI())); + + // Handle attributes + for (final Iterator it = event.getAttributes(); + it.hasNext(); ) { + + final javax.xml.stream.events.Attribute att = + (javax.xml.stream.events.Attribute)it.next(); + + final QName aqname = att.getName(); + + final Namespace attNs = Namespace.getNamespace(aqname.getPrefix(), + aqname.getNamespaceURI()); + + factory.setAttribute(element, factory.attribute( + aqname.getLocalPart(), att.getValue(), + AttributeType.getAttributeType(att.getDTDType()), attNs)); + } + + for (final Iterator it = event.getNamespaces(); it.hasNext();) { + final javax.xml.stream.events.Namespace ns = + (javax.xml.stream.events.Namespace)it.next(); + + element.addNamespaceDeclaration(Namespace.getNamespace( + ns.getPrefix(), ns.getNamespaceURI())); + } + + return element; + } + + + + /** The factory to use for parsing */ + private JDOMFactory factory = new DefaultJDOMFactory(); + + /** + * Returns the current {@link org.jdom.JDOMFactory} in use. + * @return the factory in use + */ + public JDOMFactory getFactory() { + return factory; + } + + /** + * This sets a custom JDOMFactory for the builder. Use this to build + * the tree with your own subclasses of the JDOM classes. + * + * @param factory JDOMFactory to use + */ + public void setFactory(JDOMFactory factory) { + this.factory = factory; + } + + /** + * This builds a document from the supplied + * XMLEventReader. + *

+ * The JDOMContent will be built by the current JDOMFactory. + * + * @param events XMLEventReader to read from + * @return Document resultant Document object + * @throws JDOMException when errors occur in parsing + */ + public Document build(XMLEventReader events) throws JDOMException { + return process(factory, events); + } + +} diff --git a/core/src/java/org/jdom/input/StAXStreamBuilder.java b/core/src/java/org/jdom/input/StAXStreamBuilder.java new file mode 100644 index 0000000..8ce9e62 --- /dev/null +++ b/core/src/java/org/jdom/input/StAXStreamBuilder.java @@ -0,0 +1,590 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input; + +import static javax.xml.stream.XMLStreamConstants.*; + +import java.util.ArrayList; +import java.util.List; + +import javax.xml.namespace.QName; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; + +import org.jdom.AttributeType; +import org.jdom.Content; +import org.jdom.DefaultJDOMFactory; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.Verifier; +import org.jdom.input.stax.DTDParser; +import org.jdom.input.stax.StAXFilter; + +/** + * Builds a JDOM Document from a StAX-based XMLStreamReader. + *

+ * XMLStreamReaders are pre-configured and as a result JDOM is not able to + * alter whether the input is validated, or whether the Stream has escaped + * entities or not. These (and other) characteristics are configurable by + * setting the correct features and properties on the XMLInputFactory when it + * is used to create the XMLStreamReader. + *

+ * Useful configuration to set, or know about is: + *

    + *
  • StAX streams seldom differentiate between Text and CDATA content. You + * will likely want to configure your StAX factory (XMLInputFactory) with + * http://java.sun.com/xml/stream/properties/report-cdata-event + * for the default Java StAX implementation, or the equivalent property for your + * StAX engine. + *
  • The remaining XMLInputFactory settings are likely to work fine at their + * default values. + *
  • StAX is not likely to be your best option if you want a validating + * parser, at least not with the default (built-in Java implementation in Java6 + * which does not support it). Consider a SAX parser. + *
+ *

+ * From a JDOM perspective XMLStreamReaders are more efficient than + * XMLEventReaders. Where possible use an XMLStreamReader. + *

+ * If you happen to be looking at the source code, pay careful attention to the + * imports so you know what type of instance is being processed, whether it is + * a StAX class, or a JDOM class, because there are name conflicts. + * + * @author Rolf Lear + * + */ +public class StAXStreamBuilder { + + /** + * Create a Document from an XMLStreamReader + * @param factory The {@link JDOMFactory} to use + * @param stream The XMLStreamReader to read from + * @return the parsed Document + * @throws JDOMException if there is any issue + * (XMLStreamExceptions are wrapped). + */ + private static final Document process(final JDOMFactory factory, + final XMLStreamReader stream) throws JDOMException { + try { + + int state = stream.getEventType(); + + if (START_DOCUMENT != state) { + throw new JDOMException("JDOM requires that XMLStreamReaders " + + "are at their beginning when being processed."); + } + + final Document document = factory.document(null); + + while (state != END_DOCUMENT) { + switch (state) { + + case START_DOCUMENT: + // for the + document.setBaseURI(stream.getLocation().getSystemId()); + document.setProperty("ENCODING_SCHEME", + stream.getCharacterEncodingScheme()); + document.setProperty("STANDALONE", + String.valueOf(stream.isStandalone())); + document.setProperty("ENCODING", + stream.getEncoding()); + break; + + case DTD: + document.setDocType(DTDParser.parse( + stream.getText(), factory)); + break; + + case START_ELEMENT: + document.setRootElement(processElementFragment(factory, stream)); + break; + + case END_ELEMENT: + throw new JDOMException("Unexpected XMLStream event at Document level: END_ELEMENT"); + case ENTITY_REFERENCE: + throw new JDOMException("Unexpected XMLStream event at Document level: ENTITY_REFERENCE"); + case CDATA: + throw new JDOMException("Unexpected XMLStream event at Document level: CDATA"); + case SPACE: + // Can happen when XMLInputFactory2.P_REPORT_PROLOG_WHITESPACE is set to true + document.addContent(factory.text(stream.getText())); + break; + case CHARACTERS: + final String badtxt = stream.getText(); + if (!Verifier.isAllXMLWhitespace(badtxt)) { + throw new JDOMException("Unexpected XMLStream event at Document level: CHARACTERS (" + badtxt + ")"); + } + // otherwise ignore the chars. + break; + + case COMMENT: + document.addContent( + factory.comment(stream.getText())); + break; + + case PROCESSING_INSTRUCTION: + document.addContent(factory.processingInstruction( + stream.getPITarget(), stream.getPIData())); + break; + + default: + throw new JDOMException("Unexpected XMLStream event " + state); + + } + if (stream.hasNext()) { + state = stream.next(); + } else { + throw new JDOMException("Unexpected end-of-XMLStreamReader"); + } + } + return document; + } catch (final XMLStreamException xse) { + throw new JDOMException("Unable to process XMLStream. See Cause.", xse); + } + } + + private List processFragments(JDOMFactory factory, XMLStreamReader stream, StAXFilter filter) throws JDOMException { + + int state = stream.getEventType(); + + if (START_DOCUMENT != state) { + throw new JDOMException("JDOM requires that XMLStreamReaders " + + "are at their beginning when being processed."); + } + List ret = new ArrayList(); + + int depth = 0; + String text = null; + + try { + while (stream.hasNext() && (state = stream.next()) != END_DOCUMENT) { + switch (state) { + case START_DOCUMENT: + throw new JDOMException("Illegal state for XMLStreamReader. Cannot get XML Fragment for state START_DOCUMENT" ); + case END_DOCUMENT: + throw new JDOMException("Illegal state for XMLStreamReader. Cannot get XML Fragment for state END_DOCUMENT" ); + case END_ELEMENT: + throw new JDOMException("Illegal state for XMLStreamReader. Cannot get XML Fragment for state END_ELEMENT" ); + + case START_ELEMENT: + final QName qn = stream.getName(); + if (filter.includeElement(depth, qn.getLocalPart(), + Namespace.getNamespace(qn.getPrefix(), qn.getNamespaceURI()))) { + ret.add(processPrunableElement(factory, stream, depth, filter)); + } else { + final int back = depth; + depth++; + + while (depth > back && stream.hasNext()) { + state = stream.next(); + if (state == START_ELEMENT) { + depth++; + } else if (state == END_ELEMENT) { + depth--; + } + } + } + break; + + case DTD: + if (filter.includeDocType()) { + ret.add(DTDParser.parse(stream.getText(), factory)); + } + break; + + case CDATA: + if ((text = filter.includeCDATA(depth, stream.getText())) != null) { + ret.add(factory.cdata(text)); + } + break; + + case SPACE: + case CHARACTERS: + if ((text = filter.includeText(depth, stream.getText())) != null) { + ret.add(factory.text(text)); + } + break; + + case COMMENT: + if ((text = filter.includeComment(depth, stream.getText())) != null) { + ret.add(factory.comment(text)); + } + break; + + case ENTITY_REFERENCE: + if (filter.includeEntityRef(depth, stream.getLocalName())) { + ret.add(factory.entityRef(stream.getLocalName())); + } + break; + + case PROCESSING_INSTRUCTION: + if (filter.includeProcessingInstruction(depth, stream.getPITarget())) { + ret.add(factory.processingInstruction( + stream.getPITarget(), stream.getPIData())); + } + break; + + default: + throw new JDOMException("Unexpected XMLStream event " + stream.getEventType()); + } + } + } catch (XMLStreamException e) { + throw new JDOMException("Unable to process fragments from XMLStreamReader.", e); + } + + return ret; + } + + + private static final Element processPrunableElement(final JDOMFactory factory, + final XMLStreamReader reader, final int topdepth, StAXFilter filter) + throws XMLStreamException, JDOMException { + + if (START_ELEMENT != reader.getEventType()) { + throw new JDOMException("JDOM requires that the XMLStreamReader " + + "is at the START_ELEMENT state when retrieving an " + + "Element Fragment."); + } + + final Element fragment = processElement(factory, reader); + Element current = fragment; + int depth = topdepth + 1; + String text = null; + while (depth > topdepth && reader.hasNext()) { + switch(reader.next()) { + case START_ELEMENT: + QName qn = reader.getName(); + if (!filter.pruneElement(depth, qn.getLocalPart(), + Namespace.getNamespace( + qn.getPrefix(), qn.getNamespaceURI()))) { + Element tmp = processElement(factory, reader); + current.addContent(tmp); + current = tmp; + depth++; + } else { + final int edepth = depth; + depth++; + int state = 0; + while (depth > edepth && reader.hasNext() && + (state = reader.next()) != END_DOCUMENT) { + if (state == START_ELEMENT) { + depth++; + } else if (state == END_ELEMENT) { + depth--; + } + } + } + break; + case END_ELEMENT: + current = current.getParentElement(); + depth--; + break; + case CDATA: + if ((text = filter.pruneCDATA(depth, reader.getText())) != null) { + current.addContent(factory.cdata(text)); + } + break; + + case SPACE: + case CHARACTERS: + if ((text = filter.pruneText(depth, reader.getText())) != null) { + current.addContent(factory.text(text)); + } + break; + + case COMMENT: + if ((text = filter.pruneComment(depth, reader.getText())) != null) { + current.addContent(factory.comment(text)); + } + break; + + case ENTITY_REFERENCE: + if (!filter.pruneEntityRef(depth, reader.getLocalName())) { + current.addContent(factory.entityRef(reader.getLocalName())); + } + break; + + case PROCESSING_INSTRUCTION: + if (!filter.pruneProcessingInstruction(depth, reader.getPITarget())) { + current.addContent(factory.processingInstruction( + reader.getPITarget(), reader.getPIData())); + } + break; + + default: + throw new JDOMException("Unexpected XMLStream event " + reader.getEventType()); + } + + } + + return fragment; + } + + /** + * Create a Content from an XMLStreamReader + * The stream is advanced to the event after the current event (or to the + * event after the matching END_ELEMENT for an Element fragment). + * @param factory The {@link JDOMFactory} to use + * @param stream The XMLStreamReader to read from + * @return the parsed Document + * @throws JDOMException if there is any issue + * (XMLStreamExceptions are wrapped). + */ + private static final Content processFragment(final JDOMFactory factory, + final XMLStreamReader stream) throws JDOMException { + try { + + switch (stream.getEventType()) { + + case START_DOCUMENT: + throw new JDOMException("Illegal state for XMLStreamReader. Cannot get XML Fragment for state START_DOCUMENT" ); + case END_DOCUMENT: + throw new JDOMException("Illegal state for XMLStreamReader. Cannot get XML Fragment for state END_DOCUMENT" ); + case END_ELEMENT: + throw new JDOMException("Illegal state for XMLStreamReader. Cannot get XML Fragment for state END_ELEMENT" ); + + case START_ELEMENT: + Element emt = processElementFragment(factory, stream); + stream.next(); + return emt; + + case DTD: + Content dt = DTDParser.parse(stream.getText(), factory); + stream.next(); + return dt; + + case CDATA: + Content cd = factory.cdata(stream.getText()); + stream.next(); + return cd; + + case SPACE: + case CHARACTERS: + Content txt = factory.text(stream.getText()); + stream.next(); + return txt; + + case COMMENT: + Content comment = factory.comment(stream.getText()); + stream.next(); + return comment; + + case ENTITY_REFERENCE: + Content er = factory.entityRef(stream.getLocalName()); + stream.next(); + return er; + + case PROCESSING_INSTRUCTION: + Content pi = factory.processingInstruction( + stream.getPITarget(), stream.getPIData()); + stream.next(); + return pi; + + default: + throw new JDOMException("Unexpected XMLStream event " + stream.getEventType()); + + } + } catch (final XMLStreamException xse) { + throw new JDOMException("Unable to process XMLStream. See Cause.", xse); + } + } + + private static final Element processElementFragment(final JDOMFactory factory, + final XMLStreamReader reader) throws XMLStreamException, JDOMException { + + if (START_ELEMENT != reader.getEventType()) { + throw new JDOMException("JDOM requires that the XMLStreamReader " + + "is at the START_ELEMENT state when retrieving an " + + "Element Fragment."); + } + + final Element fragment = processElement(factory, reader); + Element current = fragment; + int depth = 1; + while (depth > 0 && reader.hasNext()) { + switch(reader.next()) { + case START_ELEMENT: + Element tmp = processElement(factory, reader); + current.addContent(tmp); + current = tmp; + depth++; + break; + case END_ELEMENT: + current = current.getParentElement(); + depth--; + break; + case CDATA: + current.addContent(factory.cdata(reader.getText())); + break; + + case SPACE: + case CHARACTERS: + current.addContent(factory.text(reader.getText())); + break; + + case COMMENT: + current.addContent(factory.comment(reader.getText())); + break; + + case ENTITY_REFERENCE: + current.addContent(factory.entityRef(reader.getLocalName())); + break; + + case PROCESSING_INSTRUCTION: + current.addContent(factory.processingInstruction( + reader.getPITarget(), reader.getPIData())); + break; + + default: + throw new JDOMException("Unexpected XMLStream event " + reader.getEventType()); + } + + } + + return fragment; + } + + private static final Element processElement(final JDOMFactory factory, + final XMLStreamReader reader) { + + final Element element = factory.element(reader.getLocalName(), + Namespace.getNamespace(reader.getPrefix(), + reader.getNamespaceURI())); + + // Handle attributes + for (int i=0, len=reader.getAttributeCount(); iJDOMFactory to use + */ + public void setFactory(JDOMFactory factory) { + this.builderfactory = factory; + } + + /** + * This builds a document from the supplied + * XMLStreamReader. + *

+ * The JDOMContent will be built by the current JDOMFactory. + * + * @param reader XMLStreamReader to read from + * @return Document resultant Document object + * @throws JDOMException when errors occur in parsing + */ + public Document build(XMLStreamReader reader) throws JDOMException { + return process(builderfactory, reader); + } + + /** + * Read the entire XMLStreamReader and from it build a list of Content that + * conforms to the rules in the supplied StAXFilter. + * @param reader The XMLStreamReader to parse + * @param filter The Filter to use for the Content + * @return a List of Content that were identified by the supplied filter + * @throws JDOMException if there was a parsing problem. + */ + public List buildFragments(XMLStreamReader reader, StAXFilter filter) throws JDOMException { + return processFragments(builderfactory, reader, filter); + } + + + /** + * Read the current XML Fragment from the XMLStreamReader. + * The XMLStreamReader must be at some 'content' state, it cannot be + * at START_DOCUMENT, for example. + * @param reader The XMLStreamReader to read the next fragment from + * @return The JDOM fragment at the current position in the reader + * @throws JDOMException if there is an issue with the state of the + * XMLStreamReader or some other issue with the processing. + */ + public Content fragment(XMLStreamReader reader) throws JDOMException { + return processFragment(builderfactory, reader); + } + +} diff --git a/core/src/java/org/jdom/input/package.html b/core/src/java/org/jdom/input/package.html new file mode 100644 index 0000000..9c3f283 --- /dev/null +++ b/core/src/java/org/jdom/input/package.html @@ -0,0 +1,15 @@ + + +Classes to build JDOM documents from various sources. +

+The most commonly used builder is SAXBuilder which constructs a JDOM document +using a SAX parser and can pull content from files, streams, sockets, readers, +and so on. It can use any underlying SAX parser to handle the parsing chores. +

+DOMBuilder lets you build JDOM content from existing org.w3c.dom.* +instances. +

+The StAXStreamBuilder and StAXEventBuilder classes allow you to build JDOM +content from StAX-based XMLStreamReader and XMLEventReader instances. + + diff --git a/core/src/java/org/jdom/input/sax/AbstractReaderSchemaFactory.java b/core/src/java/org/jdom/input/sax/AbstractReaderSchemaFactory.java new file mode 100644 index 0000000..858578f --- /dev/null +++ b/core/src/java/org/jdom/input/sax/AbstractReaderSchemaFactory.java @@ -0,0 +1,131 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.Schema; + +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import org.jdom.JDOMException; + +/** + * This {@link AbstractReaderSchemaFactory} class returns XMLReaders configured to + * validate against the supplied Schema instance. The Schema could be an XSD + * schema or some other schema supported by SAX (e.g. RelaxNG). It takes a pre-declared + *

+ * If you want to validate an XML document against the XSD references embedded + * in the XML itself (xsdSchemaLocation) then you do not want to use this class + * but rather use an alternate means like + * {@link XMLReaders#XSDVALIDATING}. + *

+ * See the {@link org.jdom.input.sax package documentation} for the best + * alternatives. + * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public abstract class AbstractReaderSchemaFactory implements XMLReaderJDOMFactory { + + private final SAXParserFactory saxfac; + + /** + * XMLReader instances from this class will be configured to validate using + * the supplied Schema instance. + * + * @param fac + * The SAXParserFactory to use for creating XMLReader instances. + * @param schema + * The Schema to use for validation. + */ + public AbstractReaderSchemaFactory(final SAXParserFactory fac, final Schema schema) { + if (schema == null) { + throw new NullPointerException("Cannot create a " + + "SchemaXMLReaderFactory with a null schema"); + } + saxfac = fac; + if (saxfac != null) { + saxfac.setNamespaceAware(true); + saxfac.setValidating(false); + saxfac.setSchema(schema); + } + } + + @Override + public XMLReader createXMLReader() throws JDOMException { + if (saxfac == null) { + throw new JDOMException("It was not possible to configure a " + + "suitable XMLReader to support " + this); + } + + try { + return saxfac.newSAXParser().getXMLReader(); + } catch (SAXException e) { + throw new JDOMException( + "Could not create a new Schema-Validating XMLReader.", e); + } catch (ParserConfigurationException e) { + throw new JDOMException( + "Could not create a new Schema-Validating XMLReader.", e); + } + } + + @Override + public boolean isValidating() { + return true; + } + +} diff --git a/core/src/java/org/jdom/input/sax/AbstractReaderXSDFactory.java b/core/src/java/org/jdom/input/sax/AbstractReaderXSDFactory.java new file mode 100644 index 0000000..c36237a --- /dev/null +++ b/core/src/java/org/jdom/input/sax/AbstractReaderXSDFactory.java @@ -0,0 +1,325 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; + +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.xml.sax.SAXException; + +import org.jdom.JDOMException; + +/** + * This AbstractReaderJDOMFactory class returns XMLReaders configured to validate + * against the supplied XML Schema (XSD) instance. + *

+ * This class has var-arg constructors, accepting potentially many XSD sources. + * It is just as simple though to have a single source: + * + *

+ * File xsdfile = new File("schema.xsd");
+ * XMLReaderJDOMFactory schemafac = new XMLReaderXSDFactory(xsdfile);
+ * SAXBuilder builder = new SAXBuilder(schemafac);
+ * File xmlfile = new File("data.xml");
+ * Document validdoc = builder.build(xmlfile);
+ * 
+ * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public class AbstractReaderXSDFactory extends AbstractReaderSchemaFactory { + + /** + * Simple interface makes it easier to pass logic around in static methods. + * @author Rolf Lear + * + */ + protected interface SchemaFactoryProvider { + /** + * Return a SchemaFactory + * @return a SchemaFactory + */ + SchemaFactory getSchemaFactory(); + } + + /** + * Compile an array of String URLs in to Sources which are then compiled in + * to a single Schema + * + * @param systemID + * The source URLs to compile + * @return the resulting Schema + * @throws JDOMException + * if there is a problem with the Sources + */ + private static final Schema getSchemaFromString(final SchemaFactoryProvider sfp, + String... systemID) throws JDOMException { + if (systemID == null) { + throw new NullPointerException("Cannot specify a null input array"); + } + if (systemID.length == 0) { + throw new IllegalArgumentException("You need at least one " + + "XSD source for an XML Schema validator"); + } + Source[] urls = new Source[systemID.length]; + for (int i = 0; i < systemID.length; i++) { + if (systemID[i] == null) { + throw new NullPointerException("Cannot specify a null SystemID"); + } + urls[i] = new StreamSource(systemID[i]); + } + return getSchemaFromSource(sfp, urls); + } + + /** + * Compile an array of Files in to URLs which are then compiled in to a + * single Schema + * + * @param systemID + * The source Files to compile + * @return the resulting Schema + * @throws JDOMException + * if there is a problem with the Sources + */ + private static final Schema getSchemaFromFile(final SchemaFactoryProvider sfp, + File... systemID) throws JDOMException { + if (systemID == null) { + throw new NullPointerException("Cannot specify a null input array"); + } + if (systemID.length == 0) { + throw new IllegalArgumentException("You need at least one " + + "XSD source for an XML Schema validator"); + } + Source[] sources = new Source[systemID.length]; + for (int i = 0; i < systemID.length; i++) { + if (systemID[i] == null) { + throw new NullPointerException("Cannot specify a null SystemID"); + } + sources[i] = new StreamSource(systemID[i]); + } + return getSchemaFromSource(sfp, sources); + } + + /** + * Compile an array of URLs in to Sources which are then compiled in to a + * single Schema + * + * @param systemID + * The source URLs to compile + * @return the resulting Schema + * @throws JDOMException + * if there is a problem with the Sources + */ + private static final Schema getSchemaFromURL(final SchemaFactoryProvider sfp, + URL... systemID) throws JDOMException { + if (systemID == null) { + throw new NullPointerException("Cannot specify a null input array"); + } + if (systemID.length == 0) { + throw new IllegalArgumentException("You need at least one " + + "XSD source for an XML Schema validator"); + } + InputStream[] streams = new InputStream[systemID.length]; + try { + Source[] sources = new Source[systemID.length]; + for (int i = 0; i < systemID.length; i++) { + if (systemID[i] == null) { + throw new NullPointerException("Cannot specify a null SystemID"); + } + InputStream is = null; + try { + is = systemID[i].openStream(); + } catch (IOException e) { + throw new JDOMException("Unable to read Schema URL " + + systemID[i].toString(), e); + } + streams[i] = is; + sources[i] = new StreamSource(is, systemID[i].toString()); + } + return getSchemaFromSource(sfp, sources); + } finally { + for (InputStream is : streams) { + if (is != null) { + try { + is.close(); + } catch (IOException ioe) { + // swallow it. + } + } + } + } + } + + /** + * Compile an array of Sources in to a single Schema + * + * @param sources + * The sources to compile + * @return the resulting Schema + * @throws JDOMException + * if there is a problem with the Sources + */ + private static final Schema getSchemaFromSource(final SchemaFactoryProvider sfp, + Source... sources) throws JDOMException { + if (sources == null) { + throw new NullPointerException("Cannot specify a null input array"); + } + if (sources.length == 0) { + throw new IllegalArgumentException("You need at least one " + + "XSD Source for an XML Schema validator"); + } + try { + SchemaFactory sfac = sfp.getSchemaFactory(); + if (sfac == null) { + throw new JDOMException("Unable to create XSDSchema validator."); + } + return sfac.newSchema(sources); + } catch (SAXException e) { + String msg = Arrays.toString(sources); + throw new JDOMException("Unable to create a Schema for Sources " + + msg, e); + } + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from SystemID references. + * + * @param fac + * The SAXParserFactory used to create the XMLReader instances. + * @param sfp + * The SchemaFactoryProvider instance that gives us Schema Factories + * @param systemid + * The var-arg array of at least one SystemID reference (URL) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + */ + public AbstractReaderXSDFactory(final SAXParserFactory fac, + final SchemaFactoryProvider sfp, String... systemid) throws JDOMException { + super(fac, getSchemaFromString(sfp, systemid)); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from URL references. + * + * @param fac + * The SAXParserFactory used to create the XMLReader instances. + * @param sfp + * The SchemaFactoryProvider instance that gives us Schema Factories + * @param systemid + * The var-arg array of at least one SystemID reference (URL) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + */ + public AbstractReaderXSDFactory(final SAXParserFactory fac, + final SchemaFactoryProvider sfp, URL... systemid) throws JDOMException { + super(fac, getSchemaFromURL(sfp, systemid)); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from File references. + * + * @param fac + * The SAXParserFactory used to create the XMLReader instances. + * @param sfp + * The SchemaFactoryProvider instance that gives us Schema Factories + * @param systemid + * The var-arg array of at least one SystemID reference (File) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + */ + public AbstractReaderXSDFactory(final SAXParserFactory fac, + final SchemaFactoryProvider sfp, File... systemid) throws JDOMException { + super(fac, getSchemaFromFile(sfp, systemid)); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from Transform Source references. + * + * @param fac + * The SAXParserFactory used to create the XMLReader instances. + * @param sfp + * The SchemaFactoryProvider instance that gives us Schema Factories + * @param sources + * The var-arg array of at least one transform Source reference to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the Sources This will + * wrap a SAXException that contains the actual fault. + */ + public AbstractReaderXSDFactory(final SAXParserFactory fac, + final SchemaFactoryProvider sfp, Source... sources) throws JDOMException { + super(fac, getSchemaFromSource(sfp, sources)); + } + +} diff --git a/core/src/java/org/jdom/input/sax/BuilderErrorHandler.java b/core/src/java/org/jdom/input/sax/BuilderErrorHandler.java new file mode 100644 index 0000000..7fc130c --- /dev/null +++ b/core/src/java/org/jdom/input/sax/BuilderErrorHandler.java @@ -0,0 +1,113 @@ +/*-- + + Copyright (C) 2000-2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import org.xml.sax.*; + +/** + * The standard JDOM error handler implementation. + * + * @author Jason Hunter + */ + +public class BuilderErrorHandler implements ErrorHandler { + + /** + * This method is called when a warning has occurred; this indicates that + * while no XML rules were broken, something appears to be incorrect or + * missing. The implementation of this method here is a "no op". + * + * @param exception + * SAXParseException that occurred. + * @throws SAXException + * when things go wrong + */ + @Override + public void warning(SAXParseException exception) throws SAXException { + // nothing + } + + /** + * This method is called in response to an error that has occurred; this + * indicates that a rule was broken, typically in validation, but that + * parsing could reasonably continue. The implementation of this method here + * is to rethrow the exception. + * + * @param exception + * SAXParseException that occurred. + * @throws SAXException + * when things go wrong + */ + @Override + public void error(SAXParseException exception) throws SAXException { + throw exception; + } + + /** + * This method is called in response to a fatal error; this indicates that a + * rule has been broken that makes continued parsing either impossible or an + * almost certain waste of time. The implementation of this method here is + * to rethrow the exception. + * + * @param exception + * SAXParseException that occurred. + * @throws SAXException + * when things go wrong + */ + @Override + public void fatalError(SAXParseException exception) throws SAXException { + throw exception; + } +} diff --git a/core/src/java/org/jdom/input/sax/DefaultSAXHandlerFactory.java b/core/src/java/org/jdom/input/sax/DefaultSAXHandlerFactory.java new file mode 100644 index 0000000..c2681c8 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/DefaultSAXHandlerFactory.java @@ -0,0 +1,84 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import org.jdom.JDOMFactory; + +/** + * This SAXHandlerFactory instance provides default-configured SAXHandler + * instances for all non-custom situations. + * + * @author Rolf Lear + */ +public final class DefaultSAXHandlerFactory + implements SAXHandlerFactory { + /** + * For performance reasons it helps to use 'final' instances of classes. + * This makes the SAXHandler class a 'final' class for all normal + * SAXBuilders. It adds no other functionality. + * + * @author Rolf Lear + */ + private static final class DefaultSAXHandler extends SAXHandler { + public DefaultSAXHandler(final JDOMFactory factory) { + super(factory); + } + } + + @Override + public SAXHandler createSAXHandler(final JDOMFactory factory) { + return new DefaultSAXHandler(factory); + } +} \ No newline at end of file diff --git a/core/src/java/org/jdom/input/sax/SAXBuilderEngine.java b/core/src/java/org/jdom/input/sax/SAXBuilderEngine.java new file mode 100644 index 0000000..ee69a26 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/SAXBuilderEngine.java @@ -0,0 +1,348 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URL; + +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; + +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.input.JDOMParseException; + +/** + * Builds a JDOM document from files, streams, readers, URLs, or a SAX + * {@link org.xml.sax.InputSource} instance using a SAX parser. This Engine is + * built by the SAXBuilder based on the state of the SAXBuilder when the engine + * was produced. It is not possible to reconfigure the engine once built, but it + * can be reused many times (though not concurrently). This makes it the fastest + * way to process many multitudes of XML documents (if those documents are all + * parsed the same way). If you want to process in multiple threads you can + * safely have one SAXBuilderEngine in each thread on the condition that: + *
    + *
  1. The JDOMFactory is thread-safe (the JDOM-supplied JDOMFactories are) + *
  2. There is no XMLFilter given to the SAXBuilder, or, if there is, then it + * is thread-safe. + *
  3. If you have a custom {@link XMLReaderJDOMFactory} that it supplies a new + * instance of an XMLReader on each call (the JDOM-supplied ones all do). + *
  4. If you have a custom {@link SAXHandlerFactory} that it supplies a new + * instance of a SAXHanfler on each call (the JDOM-supplied one does) + *
+ * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public class SAXBuilderEngine implements SAXEngine { + + /** The SAX XMLReader. */ + private final XMLReader saxParser; + + /** The SAXHandler */ + private final SAXHandler saxHandler; + + /** indicates whether this is a validating parser */ + private final boolean validating; + + /** + * Creates a new SAXBuilderEngine. + * + * @param reader + * The XMLReader this Engine parses with + * @param handler + * The SAXHandler that processes the SAX Events. + * @param validating + * True if this is a validating system. + */ + public SAXBuilderEngine(final XMLReader reader, final SAXHandler handler, + final boolean validating) { + saxParser = reader; + saxHandler = handler; + this.validating = validating; + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#getJDOMFactory() + */ + @Override + public JDOMFactory getJDOMFactory() { + return saxHandler.getFactory(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#isValidating() + */ + @Override + public boolean isValidating() { + return validating; + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#getErrorHandler() + */ + @Override + public ErrorHandler getErrorHandler() { + return saxParser.getErrorHandler(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#getEntityResolver() + */ + @Override + public EntityResolver getEntityResolver() { + return saxParser.getEntityResolver(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#getDTDHandler() + */ + @Override + public DTDHandler getDTDHandler() { + return saxParser.getDTDHandler(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#isIgnoringElementContentWhitespace() + */ + @Override + public boolean getIgnoringElementContentWhitespace() { + return saxHandler.getIgnoringElementContentWhitespace(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#isIgnoringBoundaryWhitespace() + */ + @Override + public boolean getIgnoringBoundaryWhitespace() { + return saxHandler.getIgnoringBoundaryWhitespace(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#isExpandEntities() + */ + @Override + public boolean getExpandEntities() { + return saxHandler.getExpandEntities(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(org.xml.sax.InputSource) + */ + @Override + public Document build(final InputSource in) + throws JDOMException, IOException { + try { + // Parse the document. + saxParser.parse(in); + + return saxHandler.getDocument(); + } catch (final SAXParseException e) { + Document doc = saxHandler.getDocument(); + if (doc.hasRootElement() == false) { + doc = null; + } + + final String systemId = e.getSystemId(); + if (systemId != null) { + throw new JDOMParseException("Error on line " + + e.getLineNumber() + " of document " + systemId + ": " + + e.getMessage(), e, doc); + } + throw new JDOMParseException("Error on line " + + e.getLineNumber() + ": " + + e.getMessage(), e, doc); + } catch (final SAXException e) { + throw new JDOMParseException("Error in building: " + + e.getMessage(), e, saxHandler.getDocument()); + } finally { + // Explicitly nullify the handler to encourage GC + // It's a stack var so this shouldn't be necessary, but it + // seems to help on some JVMs + saxHandler.reset(); + } + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.io.InputStream) + */ + @Override + public Document build(final InputStream in) throws JDOMException, IOException { + return build(new InputSource(in)); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.io.File) + */ + @Override + public Document build(final File file) throws JDOMException, IOException { + try { + return build(fileToURL(file)); + } catch (final MalformedURLException e) { + throw new JDOMException("Error in building", e); + } + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.net.URL) + */ + @Override + public Document build(final URL url) throws JDOMException, IOException { + return build(new InputSource(url.toExternalForm())); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.io.InputStream, + * java.lang.String) + */ + @Override + public Document build(final InputStream in, final String systemId) + throws JDOMException, IOException { + + final InputSource src = new InputSource(in); + src.setSystemId(systemId); + return build(src); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.io.Reader) + */ + @Override + public Document build(final Reader characterStream) + throws JDOMException, IOException { + return build(new InputSource(characterStream)); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.io.Reader, + * java.lang.String) + */ + @Override + public Document build(final Reader characterStream, final String systemId) + throws JDOMException, IOException { + + final InputSource src = new InputSource(characterStream); + src.setSystemId(systemId); + return build(src); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.input.sax.SAXEngine#build(java.lang.String) + */ + @Override + public Document build(final String systemId) + throws JDOMException, IOException { + return build(new InputSource(systemId)); + } + + /** + * Custom File.toUrl() implementation to handle special chars in file names + * Actually, we can now rely on the core Java toURI function, + * + * @param file + * file object whose path will be converted + * @return URL form of the file, with special characters handled + * @throws MalformedURLException + * if there's a problem constructing a URL + */ + private static URL fileToURL(final File file) throws MalformedURLException { + + return file.getAbsoluteFile().toURI().toURL(); + + } + +} diff --git a/core/src/java/org/jdom/input/sax/SAXEngine.java b/core/src/java/org/jdom/input/sax/SAXEngine.java new file mode 100644 index 0000000..efb2f29 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/SAXEngine.java @@ -0,0 +1,276 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.net.URL; + +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; + +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; + +/** + * SAXEngine provides an interface to interact with either the SAXBuilder or the + * SAXBuilderEngine. This makes it possible to do pooling of SAXEngines for + * parsing using instances of either SAXBuilder or SAXBuilderEngine. + * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public interface SAXEngine { + + /** + * Returns the current {@link org.jdom.JDOMFactory} in use. + * + * @return the factory in use + */ + public abstract JDOMFactory getJDOMFactory(); + + /** + * Returns whether validation is to be performed during the build. + * + * @return whether validation is to be performed during the build + */ + public abstract boolean isValidating(); + + /** + * Returns the {@link ErrorHandler} assigned, or null if none. + * + * @return the ErrorHandler assigned, or null if none + */ + public abstract ErrorHandler getErrorHandler(); + + /** + * Returns the {@link EntityResolver} assigned, or null if none. + * + * @return the EntityResolver assigned + */ + public abstract EntityResolver getEntityResolver(); + + /** + * Returns the {@link DTDHandler} assigned, or null if none. + * + * @return the DTDHandler assigned + */ + public abstract DTDHandler getDTDHandler(); + + /** + * Returns whether element content whitespace is to be ignored during the + * build. + * + * @return whether element content whitespace is to be ignored during the + * build + */ + public abstract boolean getIgnoringElementContentWhitespace(); + + /** + * Returns whether or not the parser will elminate element content + * containing only whitespace. + * + * @return boolean - whether only whitespace content will be + * ignored during build. + */ + public abstract boolean getIgnoringBoundaryWhitespace(); + + /** + * Returns whether or not entities are being expanded into normal text + * content. + * + * @return whether entities are being expanded + */ + public abstract boolean getExpandEntities(); + + /** + * This builds a document from the supplied input source. + * + * @param in + * InputSource to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + public abstract Document build(InputSource in) + throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied input stream. + *

+ * + * @param in + * InputStream to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed. + */ + public abstract Document build(InputStream in) throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied filename. + *

+ * + * @param file + * File to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + public abstract Document build(final File file) throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied URL. + *

+ * + * @param url + * URL to read from. + * @return Document - resultant Document object. + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed. + */ + public abstract Document build(final URL url) throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied input stream. + *

+ * + * @param in + * InputStream to read from. + * @param systemId + * base for resolving relative URIs + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + public abstract Document build(final InputStream in, final String systemId) + throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied Reader. It's the programmer's + * responsibility to make sure the reader matches the encoding of the file. + * It's often easier and safer to use an InputStream rather than a Reader, + * and to let the parser auto-detect the encoding from the XML declaration. + *

+ * + * @param characterStream + * Reader to read from + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + public abstract Document build(final Reader characterStream) + throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied Reader. It's the programmer's + * responsibility to make sure the reader matches the encoding of the file. + * It's often easier and safer to use an InputStream rather than a Reader, + * and to let the parser auto-detect the encoding from the XML declaration. + *

+ * + * @param characterStream + * Reader to read from. + * @param systemId + * base for resolving relative URIs + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + public abstract Document build(final Reader characterStream, final String systemId) + throws JDOMException, IOException; + + /** + *

+ * This builds a document from the supplied URI. + *

+ * + * @param systemId + * URI for the input + * @return Document resultant Document object + * @throws JDOMException + * when errors occur in parsing + * @throws IOException + * when an I/O error prevents a document from being fully parsed + */ + public abstract Document build(final String systemId) + throws JDOMException, IOException; + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/input/sax/SAXHandler.java b/core/src/java/org/jdom/input/sax/SAXHandler.java new file mode 100644 index 0000000..9a6babe --- /dev/null +++ b/core/src/java/org/jdom/input/sax/SAXHandler.java @@ -0,0 +1,1164 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.XMLConstants; + +import org.xml.sax.Attributes; +import org.xml.sax.DTDHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.ext.Attributes2; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.DefaultHandler; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.input.SAXBuilder; + +/** + * A support class for {@link SAXBuilder} which listens for SAX events. + *

+ * People overriding this class are cautioned to ensure that the implementation + * of the cleanUp() method resets to a virgin state. The cleanUp() method will + * be called when this SAXHandler is reset(), which may happen multiple times + * between parses. The cleanUp() method must ensure that there are no references + * remaining to any external instances. + *

+ * Overriding of this class is permitted to allow for different handling of SAX + * events. Once you have created a subclass of this, you also need to create a + * custom implementation of {@link SAXHandlerFactory} to supply your instances + * to {@link SAXBuilder} + *

+ * If the XMLReader producing the SAX Events supports a document Locator, then + * this instance will use the locator to supply the line and column data from + * the SAX locator to the JDOMFactory. Note: the SAX + * specification for the SAX Locator indicates that the line and column + * represent the position of the end of the SAX Event. For + * example, the line and column of the simple XML <root /> + * would be line 1, column 9. + * + * @see org.jdom.input.sax + * @author Brett McLaughlin + * @author Jason Hunter + * @author Philip Nelson + * @author Bradley S. Huffman + * @author phil@triloggroup.com + * @author Rolf Lear + */ +public class SAXHandler extends DefaultHandler implements LexicalHandler, + DeclHandler, DTDHandler { + + /** The JDOMFactory used for JDOM object creation */ + private final JDOMFactory factory; + + /** + * Temporary holder for namespaces that have been declared with + * startPrefixMapping, but are not yet available on the element + */ + private final List declaredNamespaces = new ArrayList( + 32); + + /** Temporary holder for the internal subset */ + private final StringBuilder internalSubset = new StringBuilder(); + + /** Temporary holder for Text and CDATA */ + private final TextBuffer textBuffer = new TextBuffer(); + + /** The external entities defined in this document */ + private final Map externalEntities = new HashMap(); + + /** Document object being built - must be cleared on reset() */ + private Document currentDocument = null; + + /** Element object being built - must be cleared on reset() */ + private Element currentElement = null; + + /** The SAX Locator object provided by the parser */ + private Locator currentLocator = null; + + /** Indicator of where in the document we are - must be reset() */ + private boolean atRoot = true; + + /** + * Indicator of whether we are in the DocType. Note that the DTD consists of + * both the internal subset (inside the tag) and the external + * subset (in a separate .dtd file). - must be reset() + */ + private boolean inDTD = false; + + /** Indicator of whether we are in the internal subset - must be reset() */ + private boolean inInternalSubset = false; + + /** Indicator of whether we previously were in a CDATA - must be reset() */ + private boolean previousCDATA = false; + + /** Indicator of whether we are in a CDATA - must be reset() */ + private boolean inCDATA = false; + + /** Indicator of whether we should expand entities */ + private boolean expand = true; + + /** + * Indicator of whether we are actively suppressing (non-expanding) a + * current entity - must be reset() + */ + private boolean suppress = false; + + /** How many nested entities we're currently within - must be reset() */ + private int entityDepth = 0; // XXX may not be necessary anymore? + + /** Whether to ignore ignorable whitespace */ + private boolean ignoringWhite = false; + + /** Whether to ignore text containing all whitespace */ + private boolean ignoringBoundaryWhite = false; + + private int lastline = 0, lastcol = 0; + + /** + * This will create a new SAXHandler that listens to SAX events + * and creates a JDOM Document. The objects will be constructed using the + * default factory. + */ + public SAXHandler() { + this(null); + } + + /** + * This will create a new SAXHandler that listens to SAX events + * and creates a JDOM Document. The objects will be constructed using the + * provided factory. + * + * @param factory + * JDOMFactory to be used for constructing objects + */ + public SAXHandler(final JDOMFactory factory) { + this.factory = factory != null ? factory : new DefaultJDOMFactory(); + reset(); + } + + /** + * Override this method if you are a subclasser, and you want to clear the + * state of your SAXHandler instance in preparation for a new parse. + */ + protected void resetSubCLass() { + // override this if you subclass SAXHandler. + // it will be called after the base core SAXHandler is reset. + } + + /** + * Restore this SAXHandler to a clean state ready for another parse round. + * All internal variables are cleared to an initialized state, and then the + * resetSubClass() method is called to clear any methods that a subclass may + * need to have reset. + */ + public final void reset() { + currentLocator = null; + currentDocument = factory.document(null); + currentElement = null; + atRoot = true; + inDTD = false; + inInternalSubset = false; + previousCDATA = false; + inCDATA = false; + suppress = false; + entityDepth = 0; + declaredNamespaces.clear(); + internalSubset.setLength(0); + textBuffer.clear(); + externalEntities.clear(); + resetSubCLass(); + } + + /** + * Pushes an element onto the tree under construction. Allows subclasses to + * put content under a dummy root element which is useful for building + * content that would otherwise be a non-well formed document. + * + * @param element + * root element under which content will be built + */ + protected void pushElement(final Element element) { + if (atRoot) { + currentDocument.setRootElement(element); // XXX should we use a + // factory call? + atRoot = false; + } else { + factory.addContent(currentElement, element); + } + currentElement = element; + } + + /** + * Returns the document. Should be called after parsing is complete. + * + * @return Document - Document that was built + */ + public Document getDocument() { + return currentDocument; + } + + /** + * Returns the factory used for constructing objects. + * + * @return JDOMFactory - the factory used for constructing + * objects. + * @see #SAXHandler(org.jdom.JDOMFactory) + */ + public JDOMFactory getFactory() { + return factory; + } + + /** + * This sets whether or not to expand entities during the build. A true + * means to expand entities as normal content. A false means to leave + * entities unexpanded as EntityRef objects. The default is + * true. + * + * @param expand + * boolean indicating whether entity expansion should + * occur. + */ + public void setExpandEntities(final boolean expand) { + this.expand = expand; + } + + /** + * Returns whether or not entities will be expanded during the build. + * + * @return boolean - whether entity expansion will occur during + * build. + * @see #setExpandEntities + */ + public boolean getExpandEntities() { + return expand; + } + + /** + * Specifies whether or not the parser should elminate whitespace in element + * content (sometimes known as "ignorable whitespace") when building the + * document. Only whitespace which is contained within element content that + * has an element only content model will be eliminated (see XML Rec 3.2.1). + * For this setting to take effect requires that validation be turned on. + * The default value of this setting is false. + * + * @param ignoringWhite + * Whether to ignore ignorable whitespace + */ + public void setIgnoringElementContentWhitespace(final boolean ignoringWhite) { + this.ignoringWhite = ignoringWhite; + } + + /** + * Specifies whether or not the parser should eliminate text() nodes + * containing only whitespace when building the document. See + * {@link SAXBuilder#setIgnoringBoundaryWhitespace(boolean)}. + * + * @param ignoringBoundaryWhite + * Whether to ignore only whitespace content + */ + public void setIgnoringBoundaryWhitespace( + final boolean ignoringBoundaryWhite) { + this.ignoringBoundaryWhite = ignoringBoundaryWhite; + } + + /** + * Returns whether or not the parser will elminate element content + * containing only whitespace. + * + * @return boolean - whether only whitespace content will be + * ignored during build. + * @see #setIgnoringBoundaryWhitespace + */ + public boolean getIgnoringBoundaryWhitespace() { + return ignoringBoundaryWhite; + } + + /** + * Returns whether or not the parser will elminate whitespace in element + * content (sometimes known as "ignorable whitespace") when building the + * document. + * + * @return boolean - whether ignorable whitespace will be + * ignored during build. + * @see #setIgnoringElementContentWhitespace + */ + public boolean getIgnoringElementContentWhitespace() { + return ignoringWhite; + } + + @Override + public void startDocument() { + if (currentLocator != null) { + currentDocument.setBaseURI(currentLocator.getSystemId()); + } + } + + /** + * This is called when the parser encounters an external entity declaration. + * + * @param name + * entity name + * @param publicID + * public id + * @param systemID + * system id + * @throws SAXException + * when things go wrong + */ + @Override + public void externalEntityDecl(final String name, final String publicID, + final String systemID) throws SAXException { + // Store the public and system ids for the name + externalEntities.put(name, new String[] { publicID, systemID }); + + if (!inInternalSubset) + return; + + internalSubset.append(" \n"); + } + + /** + * This handles an attribute declaration in the internal subset. + * + * @param eName + * String element name of attribute + * @param aName + * String attribute name + * @param type + * String attribute type + * @param valueDefault + * String default value of attribute + * @param value + * String value of attribute + */ + @Override + public void attributeDecl(final String eName, final String aName, + final String type, final String valueDefault, final String value) { + + if (!inInternalSubset) + return; + + internalSubset.append(" \n"); + } + + /** + * Handle an element declaration in a DTD. + * + * @param name + * String name of element + * @param model + * String model of the element in DTD syntax + */ + @Override + public void elementDecl(final String name, final String model) { + // Skip elements that come from the external subset + if (!inInternalSubset) + return; + + internalSubset.append(" \n"); + } + + /** + * Handle an internal entity declaration in a DTD. + * + * @param name + * String name of entity + * @param value + * String value of the entity + */ + @Override + public void internalEntityDecl(final String name, final String value) { + + // Skip entities that come from the external subset + if (!inInternalSubset) + return; + + internalSubset.append(" \n"); + } + + /** + * This will indicate that a processing instruction has been encountered. + * (The XML declaration is not a processing instruction and will not be + * reported.) + * + * @param target + * String target of PI + * @param data + * String containing all data sent to the PI. This + * typically looks like one or more attribute value pairs. + * @throws SAXException + * when things go wrong + */ + @Override + public void processingInstruction(final String target, final String data) + throws SAXException { + + if (suppress) + return; + + flushCharacters(); + + final ProcessingInstruction pi = (currentLocator == null) ? factory + .processingInstruction(target, data) : factory + .processingInstruction(currentLocator.getLineNumber(), + currentLocator.getColumnNumber(), target, data); + + if (atRoot) { + factory.addContent(currentDocument, pi); + } else { + factory.addContent(getCurrentElement(), pi); + } + } + + /** + * This indicates that an unresolvable entity reference has been + * encountered, normally because the external DTD subset has not been read. + * + * @param name + * String name of entity + * @throws SAXException + * when things go wrong + */ + @Override + public void skippedEntity(final String name) throws SAXException { + + // We don't handle parameter entity references. + if (name.startsWith("%")) + return; + + flushCharacters(); + + final EntityRef er = currentLocator == null ? factory.entityRef(name) + : factory.entityRef(currentLocator.getLineNumber(), + currentLocator.getColumnNumber(), name); + + factory.addContent(getCurrentElement(), er); + } + + /** + * This will add the prefix mapping to the JDOM Document + * object. + * + * @param prefix + * String namespace prefix. + * @param uri + * String namespace URI. + */ + @Override + public void startPrefixMapping(final String prefix, final String uri) + throws SAXException { + + if (suppress) + return; + + final Namespace ns = Namespace.getNamespace(prefix, uri); + declaredNamespaces.add(ns); + } + + /** + * This reports the occurrence of an actual element. It will include the + * element's attributes, with the exception of XML vocabulary specific + * attributes, such as xmlns:[namespace prefix] and + * xsi:schemaLocation. + * + * @param namespaceURI + * String namespace URI this element is associated with, + * or an empty String + * @param localName + * String name of element (with no namespace prefix, if + * one is present) + * @param qName + * String XML 1.0 version of element name: [namespace + * prefix]:[localName] + * @param atts + * Attributes list for this element + * @throws SAXException + * when things go wrong + */ + @Override + public void startElement(final String namespaceURI, String localName, + final String qName, final Attributes atts) throws SAXException { + if (suppress) + return; + + String prefix = ""; + + // If QName is set, then set prefix and local name as necessary + if (!"".equals(qName)) { + final int colon = qName.indexOf(':'); + + if (colon > 0) { + prefix = qName.substring(0, colon); + } + + // If local name is not set, try to get it from the QName + if ((localName == null) || (localName.equals(""))) { + localName = qName.substring(colon + 1); + } + } + // At this point either prefix and localName are set correctly or + // there is an error in the parser. + + final Namespace namespace = Namespace + .getNamespace(prefix, namespaceURI); + final Element element = currentLocator == null ? factory.element( + localName, namespace) : factory.element( + currentLocator.getLineNumber(), + currentLocator.getColumnNumber(), localName, namespace); + + // Take leftover declared namespaces and add them to this element's + // map of namespaces + if (declaredNamespaces.size() > 0) { + transferNamespaces(element); + } + + flushCharacters(); + + if (atRoot) { + factory.setRoot(currentDocument, element); // Yes, use a factory + // call... + atRoot = false; + } else { + factory.addContent(getCurrentElement(), element); + } + currentElement = element; + + // Handle attributes + for (int i = 0, len = atts.getLength(); i < len; i++) { + + String attPrefix = ""; + String attLocalName = atts.getLocalName(i); + final String attQName = atts.getQName(i); + final boolean specified = (atts instanceof Attributes2) ? ((Attributes2)atts).isSpecified(i) : true; + + // If attribute QName is set, then set attribute prefix and + // attribute local name as necessary + if (!attQName.equals("")) { + // Bypass any xmlns attributes which might appear, as we got + // them already in startPrefixMapping(). This is sometimes + // necessary when SAXHandler is used with another source than + // SAXBuilder, as with JDOMResult. + if (attQName.startsWith("xmlns:") || attQName.equals("xmlns")) { + continue; + } + + final int attColon = attQName.indexOf(':'); + + if (attColon > 0) { + attPrefix = attQName.substring(0, attColon); + } + + // If localName is not set, try to get it from the QName + if ("".equals(attLocalName)) { + attLocalName = attQName.substring(attColon + 1); + } + } + + final AttributeType attType = AttributeType.getAttributeType(atts + .getType(i)); + final String attValue = atts.getValue(i); + final String attURI = atts.getURI(i); + + if (XMLConstants.XMLNS_ATTRIBUTE.equals(attLocalName) + || XMLConstants.XMLNS_ATTRIBUTE.equals(attPrefix) + || XMLConstants.XMLNS_ATTRIBUTE_NS_URI.equals(attURI)) { + // use the actual Namespace to check too, because, in theory, a + // namespace-aware parser does not need to set the qName unless + // the namespace-prefixes feature is set as well. + continue; + } + // At this point either attPrefix and attLocalName are set + // correctly or there is an error in the parser. + + // just one thing to sort out.... + // the prefix for the namespace. + if (!"".equals(attURI) && "".equals(attPrefix)) { + // the localname and qName are the same, but there is a + // Namspace URI. We need to figure out the namespace prefix. + // this is an unusual condition. Currently the only known + // trigger + // is when there is a fixed/defaulted attribute from a + // validating + // XMLSchema, and the attribute is in a different namespace + // than the rest of the document, this happens whenever there + // is an attribute definition that has form="qualified". + // + // or the schema sets attributeFormDefault="qualified" + final HashMap tmpmap = new HashMap(); + for (final Namespace nss : element.getNamespacesInScope()) { + if (nss.getPrefix().length() > 0 + && nss.getURI().equals(attURI)) { + attPrefix = nss.getPrefix(); + break; + } + tmpmap.put(nss.getPrefix(), nss); + } + + if ("".equals(attPrefix)) { + // we cannot find a 'prevailing' namespace that has a prefix + // that is for this namespace. + // This basically means that there's an XMLSchema, for the + // DEFAULT namespace, and there's a defaulted/fixed + // attribute definition in the XMLSchema that's targeted + // for this namespace,... but, the user has either not + // declared a prefixed version of the namespace, or has + // re-declared the same prefix at a lower level with a + // different namespace. + // All of these things are possible. + // Create some sort of default prefix. + int cnt = 0; + final String base = "attns"; + String pfx = base + cnt; + while (tmpmap.containsKey(pfx)) { + cnt++; + pfx = base + cnt; + } + attPrefix = pfx; + } + } + final Namespace attNs = Namespace.getNamespace(attPrefix, attURI); + + final Attribute attribute = factory.attribute(attLocalName, + attValue, attType, attNs); + if (!specified) { + // it is a DTD defaulted value. + attribute.setSpecified(false); + } + factory.setAttribute(element, attribute); + } + + } + + /** + * This will take the supplied {@link Element} and transfer its + * namespaces to the global namespace storage. + * + * @param element + * Element to read namespaces from. + */ + private void transferNamespaces(final Element element) { + for (final Namespace ns : declaredNamespaces) { + if (ns != element.getNamespace()) { + element.addNamespaceDeclaration(ns); + } + } + declaredNamespaces.clear(); + } + + /** + * This will report character data (within an element). + * + * @param ch + * char[] character array with character data + * @param start + * int index in array where data starts. + * @param length + * int length of data. + */ + @Override + public void characters(final char[] ch, final int start, final int length) + throws SAXException { + + if (suppress || (length == 0 && !inCDATA)) + return; + + if (previousCDATA != inCDATA) { + flushCharacters(); + } + + textBuffer.append(ch, start, length); + + if (currentLocator != null) { + lastline = currentLocator.getLineNumber(); + lastcol = currentLocator.getColumnNumber(); + } + } + + /** + * Capture ignorable whitespace as text. If + * setIgnoringElementContentWhitespace(true) has been called then this + * method does nothing. + * + * @param ch + * [] - char array of ignorable whitespace + * @param start + * int - starting position within array + * @param length + * int - length of whitespace after start + * @throws SAXException + * when things go wrong + */ + @Override + public void ignorableWhitespace(final char[] ch, final int start, + final int length) throws SAXException { + if (!ignoringWhite) { + characters(ch, start, length); + } + } + + /** + * This will flush any characters from SAX character calls we've been + * buffering. + * + * @throws SAXException + * when things go wrong + */ + protected void flushCharacters() throws SAXException { + if (ignoringBoundaryWhite) { + if (!textBuffer.isAllWhitespace()) { + flushCharacters(textBuffer.toString()); + } + } else { + flushCharacters(textBuffer.toString()); + } + textBuffer.clear(); + } + + /** + * Flush the given string into the document. This is a protected method so + * subclassers can control text handling without knowledge of the internals + * of this class. + * + * @param data + * string to flush + * @throws SAXException + * if the state of the handler does not allow this. + */ + protected void flushCharacters(final String data) throws SAXException { + if (data.length() == 0 && !inCDATA) { + previousCDATA = inCDATA; + return; + } + + /** + * This is commented out because of some problems with the inline DTDs + * that Xerces seems to have. if (!inDTD) { if (inEntity) { + * getCurrentElement().setContent(factory.text(data)); } else { + * getCurrentElement().addContent(factory.text(data)); } + */ + + if (previousCDATA) { + final CDATA cdata = currentLocator == null ? factory.cdata(data) + : factory.cdata(lastline, lastcol, data); + factory.addContent(getCurrentElement(), cdata); + } else if (data.length() != 0) { + final Text text = currentLocator == null ? factory.text(data) + : factory.text(lastline, lastcol, data); + factory.addContent(getCurrentElement(), text); + } + + previousCDATA = inCDATA; + } + + /** + * Indicates the end of an element (</[element name]>) is + * reached. Note that the parser does not distinguish between empty elements + * and non-empty elements, so this will occur uniformly. + * + * @param namespaceURI + * String URI of namespace this element is associated + * with + * @param localName + * String name of element without prefix + * @param qName + * String name of element in XML 1.0 form + * @throws SAXException + * when things go wrong + */ + @Override + public void endElement(final String namespaceURI, final String localName, + final String qName) throws SAXException { + + if (suppress) + return; + + flushCharacters(); + + if (!atRoot) { + final Parent p = currentElement.getParent(); + if (p instanceof Document) { + atRoot = true; + } else { + currentElement = (Element) p; + } + } else { + throw new SAXException( + "Ill-formed XML document (missing opening tag for " + + localName + ")"); + } + } + + /** + * This will signify that a DTD is being parsed, and can be used to ensure + * that comments and other lexical structures in the DTD are not added to + * the JDOM Document object. + * + * @param name + * String name of element listed in DTD + * @param publicID + * String public ID of DTD + * @param systemID + * String system ID of DTD + */ + @Override + public void startDTD(final String name, final String publicID, + final String systemID) throws SAXException { + + flushCharacters(); // Is this needed here? + + final DocType doctype = currentLocator == null ? factory.docType(name, + publicID, systemID) : factory.docType( + currentLocator.getLineNumber(), + currentLocator.getColumnNumber(), name, publicID, systemID); + factory.addContent(currentDocument, doctype); + inDTD = true; + inInternalSubset = true; + } + + /** + * This signifies that the reading of the DTD is complete. + */ + @Override + public void endDTD() { + + currentDocument.getDocType().setInternalSubset( + internalSubset.toString()); + inDTD = false; + inInternalSubset = false; + } + + @Override + public void startEntity(final String name) throws SAXException { + entityDepth++; + + if (expand || entityDepth > 1) { + // Short cut out if we're expanding or if we're nested + return; + } + + // A "[dtd]" entity indicates the beginning of the external subset + if (name.equals("[dtd]")) { + inInternalSubset = false; + return; + } + + // Ignore DTD references, and translate the standard 5 + if ((!inDTD) && (!name.equals("amp")) && (!name.equals("lt")) + && (!name.equals("gt")) && (!name.equals("apos")) + && (!name.equals("quot"))) { + + if (!expand) { + String pub = null; + String sys = null; + final String[] ids = externalEntities.get(name); + if (ids != null) { + pub = ids[0]; // may be null, that's OK + sys = ids[1]; // may be null, that's OK + } + /** + * if no current element, this entity belongs to an attribute in + * these cases, it is an error on the part of the parser to call + * startEntity but this will help in some cases. See + * org/xml/sax/ + * ext/LexicalHandler.html#startEntity(java.lang.String) for + * more information + */ + if (!atRoot) { + flushCharacters(); + final EntityRef entity = currentLocator == null ? factory + .entityRef(name, pub, sys) : factory.entityRef( + currentLocator.getLineNumber(), + currentLocator.getColumnNumber(), name, pub, sys); + + // no way to tell if the entity was from an attribute or + // element so just assume element + factory.addContent(getCurrentElement(), entity); + } + suppress = true; + } + } + } + + @Override + public void endEntity(final String name) throws SAXException { + entityDepth--; + if (entityDepth == 0) { + // No way are we suppressing if not in an entity, + // regardless of the "expand" value + suppress = false; + } + if (name.equals("[dtd]")) { + inInternalSubset = true; + } + } + + /** + * Report a CDATA section + */ + @Override + public void startCDATA() { + if (suppress) + return; + + inCDATA = true; + } + + /** + * Report a CDATA section + */ + @Override + public void endCDATA() throws SAXException { + if (suppress) + return; + + previousCDATA = true; + flushCharacters(); + previousCDATA = false; + inCDATA = false; + } + + /** + * This reports that a comments is parsed. If not in the DTD, this comment + * is added to the current JDOM Element, or the + * Document itself if at that level. + * + * @param ch + * ch[] array of comment characters. + * @param start + * int index to start reading from. + * @param length + * int length of data. + * @throws SAXException + * if the state of the handler disallows this call + */ + @Override + public void comment(final char[] ch, final int start, final int length) + throws SAXException { + + if (suppress) + return; + + flushCharacters(); + + final String commentText = new String(ch, start, length); + if (inDTD && inInternalSubset && (expand == false)) { + internalSubset.append(" \n"); + return; + } + if ((!inDTD) && (!commentText.equals(""))) { + final Comment comment = currentLocator == null ? factory + .comment(commentText) : factory.comment( + currentLocator.getLineNumber(), + currentLocator.getColumnNumber(), commentText); + if (atRoot) { + factory.addContent(currentDocument, comment); + } else { + factory.addContent(getCurrentElement(), comment); + } + } + } + + /** + * Handle the declaration of a Notation in a DTD + * + * @param name + * name of the notation + * @param publicID + * the public ID of the notation + * @param systemID + * the system ID of the notation + */ + @Override + public void notationDecl(final String name, final String publicID, + final String systemID) throws SAXException { + + if (!inInternalSubset) + return; + + internalSubset.append(" \n"); + } + + /** + * Handler for unparsed entity declarations in the DTD + * + * @param name + * String of the unparsed entity decl + * @param publicID + * String of the unparsed entity decl + * @param systemID + * String of the unparsed entity decl + * @param notationName + * String of the unparsed entity decl + */ + @Override + public void unparsedEntityDecl(final String name, final String publicID, + final String systemID, final String notationName) { + + if (!inInternalSubset) + return; + + internalSubset.append(" \n"); + } + + /** + * Appends an external ID to the internal subset buffer. Either publicID or + * systemID may be null, but not both. + * + * @param publicID + * the public ID + * @param systemID + * the system ID + */ + private void appendExternalId(final String publicID, final String systemID) { + if (publicID != null) { + internalSubset.append(" PUBLIC \"").append(publicID).append('\"'); + } + if (systemID != null) { + if (publicID == null) { + internalSubset.append(" SYSTEM "); + } else { + internalSubset.append(' '); + } + internalSubset.append('\"').append(systemID).append('\"'); + } + } + + /** + * Returns the being-parsed element. + * + * @return Element - element being built. + * @throws SAXException + * if the state of the handler disallows this call + */ + public Element getCurrentElement() throws SAXException { + if (currentElement == null) { + throw new SAXException( + "Ill-formed XML document (multiple root elements detected)"); + } + return currentElement; + } + + /** + * Receives an object for locating the origin of SAX document events. This + * method is invoked by the SAX parser. + *

+ * {@link org.jdom.JDOMFactory} implementations can use the + * {@link #getDocumentLocator} method to get access to the {@link Locator} + * during parse. + *

+ * + * @param locator + * Locator an object that can return the location of any + * SAX document event. + */ + @Override + public void setDocumentLocator(final Locator locator) { + this.currentLocator = locator; + } + + /** + * Provides access to the {@link Locator} object provided by the SAX parser. + * + * @return Locator an object that can return the location of + * any SAX document event. + */ + public Locator getDocumentLocator() { + return currentLocator; + } +} diff --git a/core/src/java/org/jdom/input/sax/SAXHandlerFactory.java b/core/src/java/org/jdom/input/sax/SAXHandlerFactory.java new file mode 100644 index 0000000..d56a8e3 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/SAXHandlerFactory.java @@ -0,0 +1,78 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import org.jdom.JDOMFactory; + +/** + * Provides SAXBuilder with SAXHandler instances to support SAX parsing. Users + * wanting to customise the way that JDOM processes the SAX events should + * override the {@link SAXHandler} class, and then use an implementation of this + * factory to feed instances of that new class to SAXBuilder. + * + * @see SAXHandler + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public interface SAXHandlerFactory { + /** + * Create a new SAXHandler instance for SAXBuilder to use. + * + * @param factory + * The {@link JDOMFactory} to use for creating JDOM content. + * @return a new instance of a SAXHandler (or subclass) class. + */ + public SAXHandler createSAXHandler(JDOMFactory factory); +} diff --git a/core/src/java/org/jdom/input/sax/TextBuffer.java b/core/src/java/org/jdom/input/sax/TextBuffer.java new file mode 100644 index 0000000..159a6dc --- /dev/null +++ b/core/src/java/org/jdom/input/sax/TextBuffer.java @@ -0,0 +1,141 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import org.jdom.Verifier; +import org.jdom.internal.ArrayCopy; + +/** + * A non-public utility class similar to StringBuilder but optimized for XML + * parsing where the common case is that you get only one chunk of characters + * per text section. TextBuffer stores the first chunk of characters in a + * String, which can just be returned directly if no second chunk is received. + * Subsequent chunks are stored in a supplemental char array (like StringBuilder + * uses). In this case, the returned text will be the first String chunk, + * concatenated with the subsequent chunks stored in the char array. This + * provides optimal performance in the common case, while still providing very + * good performance in the uncommon case. Furthermore, avoiding StringBuilder + * means that no extra unused char array space will be kept around after parsing + * is through. + * + * @author Bradley S. Huffman + * @author Alex Rosen + */ +final class TextBuffer { + + /** + * The text value. Only the first arraySize characters are + * valid. + */ + private char[] array = new char[1024]; + + /** The size of the text value. */ + private int arraySize = 0; + + /** Constructor */ + TextBuffer() { + } + + /** + * Append the specified text to the text value of this buffer. + * + * @param source + * The char[] data to add + * @param start + * The offset in the data to start adding from + * @param count + * The number of chars to add. + */ + void append(final char[] source, final int start, final int count) { + if ((count + arraySize) > array.length) { + // Fixes #112 + array = ArrayCopy.copyOf(array, count + arraySize + (array.length >> 2)); + } + System.arraycopy(source, start, array, arraySize, count); + arraySize += count; + } + + /** + * Clears the text value and prepares the TextBuffer for reuse. + */ + void clear() { + arraySize = 0; + } + + /** + * Inspects the character data for non-whitespace + * + * @return true if all chars are whitespace + */ + boolean isAllWhitespace() { + int i = arraySize; + while (--i >= 0) { + if (!Verifier.isXMLWhitespace(array[i])) { + return false; + } + } + return true; + } + + /** Returns the text value stored in the buffer. */ + @Override + public String toString() { + if (arraySize == 0) { + return ""; + } + return String.valueOf(array, 0, arraySize); + } + +} diff --git a/core/src/java/org/jdom/input/sax/XMLReaderJAXPFactory.java b/core/src/java/org/jdom/input/sax/XMLReaderJAXPFactory.java new file mode 100644 index 0000000..972f910 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/XMLReaderJAXPFactory.java @@ -0,0 +1,118 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import org.jdom.JDOMException; + +/** + * Create XMLReaders directly from the javax.xml.parsers.SAXParserFactory API using an explicit + * implementation of the parser instead of relying on the default JAXP search path. + *

+ * If you want to rely on the default JAXP search mechanism you should instead use one of the simple + * members of the {@link XMLReaders} enumeration, or use one of the simple constructors on + * {@link XMLReaderXSDFactory} or {@link XMLReaderSchemaFactory}. + *

+ * See the documentation for {@link SAXParserFactory} for the details on what the factoryClassName + * and classLoader should be. + * + * @see org.jdom.input.sax + * @since 2.0.3 + * @author Rolf Lear + */ +public class XMLReaderJAXPFactory implements XMLReaderJDOMFactory { + + private final SAXParserFactory instance; + private final boolean validating; + + /** + * Create an XMLReaderJAXPFactory using the specified factory name, classloader, and + * dtdvalidating flag. + * @param factoryClassName The name of the implementation to use for the SAXParserFactory. + * @param classLoader The classloader to use for locating the SAXParserFactory (may be null). + * @param dtdvalidate Whether this should create DTD Validating XMLReaders. + */ + public XMLReaderJAXPFactory(final String factoryClassName, final ClassLoader classLoader, + boolean dtdvalidate) { + instance = SAXParserFactory.newInstance(factoryClassName, classLoader); + instance.setNamespaceAware(true); + instance.setValidating(dtdvalidate); + validating = dtdvalidate; + } + + @Override + public XMLReader createXMLReader() throws JDOMException { + try { + return instance.newSAXParser().getXMLReader(); + } catch (SAXException e) { + throw new JDOMException( + "Unable to create a new XMLReader instance", e); + } catch (ParserConfigurationException e) { + throw new JDOMException( + "Unable to create a new XMLReader instance", e); + } + } + + @Override + public boolean isValidating() { + return validating; + } + +} diff --git a/core/src/java/org/jdom/input/sax/XMLReaderJDOMFactory.java b/core/src/java/org/jdom/input/sax/XMLReaderJDOMFactory.java new file mode 100644 index 0000000..f901dca --- /dev/null +++ b/core/src/java/org/jdom/input/sax/XMLReaderJDOMFactory.java @@ -0,0 +1,91 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import org.xml.sax.XMLReader; + +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; + +/** + * This interface can be used to supply custom XMLReaders to {@link SAXBuilder}. + *

+ * See the {@link org.jdom.input.sax package documentation} for details on what + * XMLReaderJDOMFactory implementations are available and when they are + * recommended. + * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public interface XMLReaderJDOMFactory { + /** + * Return a new XMLReader according to the implementation of this + * XMLReaderJDOMFactory instance. The XMLReader is expected to be a new + * instance that is unrelated to any other XMLReaders, and can be reused at + * will by {@link SAXBuilder}. + * + * @return a new XMLReader + * @throws JDOMException + * if an XMLReader was not available. + */ + public XMLReader createXMLReader() throws JDOMException; + + /** + * Does an XMLReader from this factory do more than just well-formed checks. + * + * @return true if the XMLReader validates + */ + public boolean isValidating(); +} diff --git a/core/src/java/org/jdom/input/sax/XMLReaderSAX2Factory.java b/core/src/java/org/jdom/input/sax/XMLReaderSAX2Factory.java new file mode 100644 index 0000000..0bbc713 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/XMLReaderSAX2Factory.java @@ -0,0 +1,148 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import static org.jdom.JDOMConstants.*; + +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; + +import org.jdom.JDOMException; + +/** + * Create XMLReaders directly from the SAX2.0 API using a SAX Driver class name + * or the default SAX2.0 location process. + *

+ * Unless you have good reason to use this mechanism you should rather use the + * JAXP-based processes. Read the {@link org.jdom.input.sax package + * documentation} for other alternatives. + * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public class XMLReaderSAX2Factory implements XMLReaderJDOMFactory { + + private final boolean validate; + private final String saxdriver; + + /** + * The required details for SAX2.0-based XMLReader creation. + * + * @param validate + * whether to validate against the DocType + * @see XMLReaders#NONVALIDATING + * @see XMLReaders#DTDVALIDATING + * @see XMLReaders#XSDVALIDATING + */ + public XMLReaderSAX2Factory(boolean validate) { + this(validate, null); + } + + /** + * The required details for SAX2.0-based XMLReader creation. + * + * @param validate + * whether to validate against the DocType + * @param saxdriver + * The SAX2.0 Driver classname (null to use the SAX2.0 default parser + * searching algorithm - if you specify null you should probably be + * using JAXP anyway). + * @see XMLReaders#NONVALIDATING + * @see XMLReaders#DTDVALIDATING + * @see XMLReaders#XSDVALIDATING + */ + public XMLReaderSAX2Factory(final boolean validate, final String saxdriver) { + super(); + this.validate = validate; + this.saxdriver = saxdriver; + } + + @Override + public XMLReader createXMLReader() throws JDOMException { + try { + XMLReader reader = saxdriver == null + ? XMLReaderFactory.createXMLReader() + : XMLReaderFactory.createXMLReader(saxdriver); + reader.setFeature( + SAX_FEATURE_VALIDATION, validate); + // Setup some namespace features. + reader.setFeature( + SAX_FEATURE_NAMESPACES, true); + reader.setFeature( + SAX_FEATURE_NAMESPACE_PREFIXES, true); + + return reader; + } catch (SAXException e) { + throw new JDOMException("Unable to create SAX2 XMLReader.", e); + } + + } + + /** + * Get the SAX Driver class name used to boostrap XMLReaders. + * + * @return The name of the SAX Driver class (null for SAX2 default class). + */ + public String getDriverClassName() { + return saxdriver; + } + + @Override + public boolean isValidating() { + return validate; + } + +} diff --git a/core/src/java/org/jdom/input/sax/XMLReaderSchemaFactory.java b/core/src/java/org/jdom/input/sax/XMLReaderSchemaFactory.java new file mode 100644 index 0000000..8d1b939 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/XMLReaderSchemaFactory.java @@ -0,0 +1,105 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.Schema; + +/** + * This {@link XMLReaderJDOMFactory} class returns XMLReaders configured to + * validate against the supplied Schema instance. The Schema could be an XSD + * schema or some other schema supported by SAX (e.g. RelaxNG). The SAX Parser + * is obtained through the JAXP process. + *

+ * If you want to validate an XML document against the XSD references embedded + * in the XML itself (xsdSchemaLocation) then you do not want to use this class + * but rather use an alternate means like + * {@link XMLReaders#XSDVALIDATING}. + *

+ * See the {@link org.jdom.input.sax package documentation} for the best + * alternatives. + * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public class XMLReaderSchemaFactory extends AbstractReaderSchemaFactory { + + /** + * XMLReader instances from this class will be configured to validate using + * the supplied Schema instance. + * + * @param schema + * The Schema to use for validation. + */ + public XMLReaderSchemaFactory(final Schema schema) { + super(SAXParserFactory.newInstance(), schema); + } + + /** + * XMLReader instances from this class will be configured to validate using + * the supplied Schema instance, and use the specified JAXP SAXParserFactory. + * + * @param factoryClassName The name of the SAXParserFactory class to use + * @param classloader The classLoader to use for loading the SAXParserFactory. + * @param schema + * The Schema to use for validation. + * @since 2.0.3 + */ + public XMLReaderSchemaFactory(final String factoryClassName, final ClassLoader classloader, + final Schema schema) { + super(SAXParserFactory.newInstance(factoryClassName, classloader), schema); + } + +} diff --git a/core/src/java/org/jdom/input/sax/XMLReaderXSDFactory.java b/core/src/java/org/jdom/input/sax/XMLReaderXSDFactory.java new file mode 100644 index 0000000..80cf9a6 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/XMLReaderXSDFactory.java @@ -0,0 +1,248 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import java.io.File; +import java.net.URL; + +import javax.xml.XMLConstants; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.validation.SchemaFactory; + +import org.jdom.JDOMException; + +/** + * This XMLReaderJDOMFactory class returns XMLReaders configured to validate + * against the supplied XML Schema (XSD) instance. The SAX Parser is obtained through + * the JAXP process. + * + *

+ * This class has var-arg constructors, accepting potentially many XSD sources. + * It is just as simple though to have a single source: + * + *

+ * File xsdfile = new File("schema.xsd");
+ * XMLReaderJDOMFactory schemafac = new XMLReaderXSDFactory(xsdfile);
+ * SAXBuilder builder = new SAXBuilder(schemafac);
+ * File xmlfile = new File("data.xml");
+ * Document validdoc = builder.build(xmlfile);
+ * 
+ * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public class XMLReaderXSDFactory extends AbstractReaderXSDFactory { + + private static final SchemaFactoryProvider xsdschemas = new SchemaFactoryProvider() { + /** + * Use a Thread-Local system to manage SchemaFactory. SchemaFactory is not + * thread-safe, so we need some mechanism to isolate it, and thread-local is + * a logical way because it only creates an instance when needed in each + * thread, and they die when the thread dies. Does not need any + * synchronisation either. + */ + private final ThreadLocal schemafactl = new ThreadLocal(); + + @Override + public SchemaFactory getSchemaFactory() { + SchemaFactory sfac = schemafactl.get(); + if (sfac == null) { + sfac = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + schemafactl.set(sfac); + } + return sfac; + } + }; + + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from SystemID references. + * + * @param systemid + * The var-arg array of at least one SystemID reference (URL) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + */ + public XMLReaderXSDFactory(String... systemid) + throws JDOMException { + super(SAXParserFactory.newInstance(), xsdschemas, systemid); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from SystemID references, and use the specified JAXP SAXParserFactory. + * + * @param factoryClassName The name of the SAXParserFactory class to use + * @param classloader The classLoader to use for loading the SAXParserFactory. + * @param systemid + * The var-arg array of at least one SystemID reference (URL) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + * @since 2.0.3 + */ + public XMLReaderXSDFactory(final String factoryClassName, final ClassLoader classloader, + final String... systemid) throws JDOMException { + super(SAXParserFactory.newInstance(factoryClassName, classloader), xsdschemas, systemid); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from URL references. + * + * @param systemid + * The var-arg array of at least one SystemID reference (URL) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + */ + public XMLReaderXSDFactory(URL... systemid) throws JDOMException { + super(SAXParserFactory.newInstance(), xsdschemas, systemid); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from URL references, and use the specified JAXP SAXParserFactory. + * + * @param factoryClassName The name of the SAXParserFactory class to use + * @param classloader The classLoader to use for loading the SAXParserFactory. + * @param systemid + * The var-arg array of at least one SystemID reference (URL) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + * @since 2.0.3 + */ + public XMLReaderXSDFactory(final String factoryClassName, final ClassLoader classloader, + URL... systemid) throws JDOMException { + super(SAXParserFactory.newInstance(factoryClassName, classloader), xsdschemas, systemid); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from File references. + * + * @param systemid + * The var-arg array of at least one SystemID reference (File) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + */ + public XMLReaderXSDFactory(File... systemid) throws JDOMException { + super(SAXParserFactory.newInstance(), xsdschemas, systemid); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from File references, and use the specified JAXP SAXParserFactory. + * + * @param factoryClassName The name of the SAXParserFactory class to use + * @param classloader The classLoader to use for loading the SAXParserFactory. + * @param systemid + * The var-arg array of at least one SystemID reference (File) to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the SystemIDs This will + * wrap a SAXException that contains the actual fault. + * @since 2.0.3 + */ + public XMLReaderXSDFactory(final String factoryClassName, final ClassLoader classloader, + File... systemid) throws JDOMException { + super(SAXParserFactory.newInstance(factoryClassName, classloader), xsdschemas, systemid); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from Transform Source references. + * + * @param sources + * The var-arg array of at least one transform Source reference to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the Sources This will + * wrap a SAXException that contains the actual fault. + */ + public XMLReaderXSDFactory(Source... sources) throws JDOMException { + super(SAXParserFactory.newInstance(), xsdschemas, sources); + } + + /** + * Create an XML Schema validating XMLReader factory using one or more XSD + * sources from Transform Source references, and use the specified JAXP SAXParserFactory. + * + * @param factoryClassName The name of the SAXParserFactory class to use + * @param classloader The classLoader to use for loading the SAXParserFactory. + * @param sources + * The var-arg array of at least one transform Source reference to + * locate the XSD's used to validate + * @throws JDOMException + * If the Schemas could not be loaded from the Sources This will + * wrap a SAXException that contains the actual fault. + * @since 2.0.3 + */ + public XMLReaderXSDFactory(final String factoryClassName, final ClassLoader classloader, + Source... sources) throws JDOMException { + super(SAXParserFactory.newInstance(factoryClassName, classloader), xsdschemas, sources); + } + +} diff --git a/core/src/java/org/jdom/input/sax/XMLReaders.java b/core/src/java/org/jdom/input/sax/XMLReaders.java new file mode 100644 index 0000000..fb515f5 --- /dev/null +++ b/core/src/java/org/jdom/input/sax/XMLReaders.java @@ -0,0 +1,265 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.sax; + +import javax.xml.XMLConstants; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import org.jdom.JDOMException; + +/** + * An enumeration of {@link XMLReaderJDOMFactory} that allows for a single + * central location to create XMLReaders. The Singletons (members) of this + * enumeration can produce the most common XMLReaders: non-validating, XSD + * validating, and DocType validating. + *

+ * See the {@link org.jdom.input.sax package documentation} for details of how + * to create the SAXParser you desire. + * + * @see org.jdom.input.sax + * @author Rolf Lear + */ +public enum XMLReaders implements XMLReaderJDOMFactory { + + /** + * The non-validating singleton + */ + NONVALIDATING(0), + + /** + * The DTD-validating Singleton + */ + DTDVALIDATING(1), + + /** + * The XSD-validating Singleton + */ + XSDVALIDATING(2); + + private interface FactorySupplier { + SAXParserFactory supply() throws Exception; + + boolean validates(); + } + + private enum NONSingleton implements FactorySupplier { + INSTANCE; + + private final SAXParserFactory factory; + + NONSingleton() { + SAXParserFactory fac = SAXParserFactory.newInstance(); + // All JDOM parsers are namespace aware. + fac.setNamespaceAware(true); + fac.setValidating(false); + factory = fac; + } + + @Override + public SAXParserFactory supply() throws Exception { + return factory; + } + + @Override + public boolean validates() { + return false; + } + + } + + private enum DTDSingleton implements FactorySupplier { + INSTANCE; + + private final SAXParserFactory factory; + + DTDSingleton() { + SAXParserFactory fac = SAXParserFactory.newInstance(); + // All JDOM parsers are namespace aware. + fac.setNamespaceAware(true); + // factory is validating (DTD) + fac.setValidating(true); + factory = fac; + } + + @Override + public SAXParserFactory supply() throws Exception { + return factory; + } + + @Override + public boolean validates() { + return true; + } + + } + + private enum XSDSingleton implements FactorySupplier { + INSTANCE; + + private final Exception failcause; + private final SAXParserFactory factory; + + XSDSingleton() { + + SAXParserFactory fac = SAXParserFactory.newInstance(); + Exception problem = null; + // All JDOM parsers are namespace aware. + fac.setNamespaceAware(true); + // factory is not validating (DTD), but the Reader is validating + // (XSD) + fac.setValidating(false); + + try { + SchemaFactory sfac = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = sfac.newSchema(); + fac.setSchema(schema); + } catch (SAXException se) { + // we could not get a validating system, set the fac to null + fac = null; + problem = se; + } catch (IllegalArgumentException iae) { + // this system does not support XSD Validation.... which is true + // for android! + // we could not get a validating system, set the fac to null + fac = null; + problem = iae; + } catch (UnsupportedOperationException uoe) { + // SAXParserFactory throws this exception when setSchema is + // called. + // Therefore every factory throws this exception unless it + // overrides + // setSchema. A popular example is Apache Xerces + // SAXParserFactoryImpl + // before version 2.7.0. + fac = null; + problem = uoe; + } + factory = fac; + failcause = problem; + + } + + @Override + public SAXParserFactory supply() throws Exception { + if (factory == null) { + throw failcause; + } + return factory; + } + + @Override + public boolean validates() { + return true; + } + + }; + + /** the actual SAXParserFactory in the respective singletons. */ + private final int singletonID; + + /** Private constructor */ + private XMLReaders(int singletonID) { + this.singletonID = singletonID; + } + + private FactorySupplier getSupplier() { + switch (singletonID) { + case 0: + return NONSingleton.INSTANCE; + case 1: + return DTDSingleton.INSTANCE; + case 2: + return XSDSingleton.INSTANCE; + } + throw new IllegalStateException("Unknown singletonID: " + singletonID); + } + + /** + * Get a new XMLReader from this JAXP-based {@link XMLReaderJDOMFactory}. + *

+ * + * @return a new XMLReader instance. + * @throws JDOMException + * if there is a problem creating the XMLReader + */ + @Override + public XMLReader createXMLReader() throws JDOMException { + try { + FactorySupplier supplier = getSupplier(); + return supplier.supply().newSAXParser().getXMLReader(); + } catch (SAXException e) { + throw new JDOMException( + "Unable to create a new XMLReader instance", e); + } catch (ParserConfigurationException e) { + throw new JDOMException( + "Unable to create a new XMLReader instance", e); + } catch (Exception e) { + throw new JDOMException("It was not possible to configure a " + + "suitable XMLReader to support " + this, e); + } + } + + @Override + public boolean isValidating() { + return getSupplier().validates(); + } + +} diff --git a/core/src/java/org/jdom/input/sax/package-info.java b/core/src/java/org/jdom/input/sax/package-info.java new file mode 100644 index 0000000..5af534d --- /dev/null +++ b/core/src/java/org/jdom/input/sax/package-info.java @@ -0,0 +1,372 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +/** + Support classes for building JDOM documents and content using SAX parsers. + +

Introduction

+ Skip to the Examples section for a quick bootstrap. +

+ The {@link org.jdom.input.SAXBuilder} class parses input and produces JDOM + output. It does this using three 'pillars' of functionality, which when combined + constitute a 'parse'. +

+ The three pillars are: +

    +
  1. The SAX Parser - this is a 'third-party' parser such as Xerces. +
  2. The SAX Event Handler - which reads the data produced by the parser +
  3. The JDOMFactory - which converts the resulting data in to JDOM content +
+ There are many different ways of parsing the document from its input state + (DocType-validating, etc.), and there are also different ways to interpret + the SAX events. Finally there are different ways to produce JDOM Content using + different implementations of the JDOMFactory. +

+ SAXBuilder provides a central location where these three pillars are configured. + Some configuration settings require coordinated changes to both the SAX parser + and the SAX handler, and SAXBuilder ensures the coordination is maintained. + +

Setting the Pillars

+ SAXBuilder provides a number of different mechanisms for stipulating what the + three pillars will be: +
    +
  • A Constructor: {@link org.jdom.input.SAXBuilder#SAXBuilder(XMLReaderJDOMFactory, SAXHandlerFactory, org.jdom.JDOMFactory)} +
  • A default constructor {@link org.jdom.input.SAXBuilder#SAXBuilder()} + that chooses a non-validating JAXP sourced XMLReader Factory + {@link org.jdom.input.sax.XMLReaders#NONVALIDATING} which it + mates with a Default {@link org.jdom.input.sax.SAXHandler} factory, and the + {@link org.jdom.DefaultJDOMFactory}. +
  • A number of other constructors that are mostly for backward-compatibility + with JDOM 1.x. These other constructors affect what + {@link org.jdom.input.sax.XMLReaderJDOMFactory} will be used but still use + the default SAXHandler and JDOMFactory values. +
  • Methods to change whatever was constructed: +
      +
    • {@link org.jdom.input.SAXBuilder#setXMLReaderFactory(XMLReaderJDOMFactory)} +
    • {@link org.jdom.input.SAXBuilder#setSAXHandlerFactory(SAXHandlerFactory)} +
    • {@link org.jdom.input.SAXBuilder#setJDOMFactory(org.jdom.JDOMFactory)} +
    +
+ + +

The XMLReaderJDOMFactory Pillar

+ + A brief history of XML Parsers in Java:
+ XML Parsers have been available in Java from essentially 'the beginning'. There + have been a number different ways to access these parsers though: +
    +
  1. Create the parser directly 'by name'. +
  2. Use the SAX (and later the SAX 2.0) API to locate a parser. +
  3. Use JAXP (versions 1, through 1.4) API to locate a parser. +
+

+ In addition to the different ways of creating an XML parser, there have also + been updates to the way the actual SAX parsing API is exposed to Java (the Java + interface). The SAX specification was revised with version 2.0. The 'new' SAX + version introduced the XMLReader concept, which replaces the XMLParser concept. + These two concepts aim to accomplish the same goal, but do it in different + ways. +

+ JDOM 2.x requires an XMLReader (SAX 2.0) interface, thus your XML parser needs + to be compatible with SAX 2.0 (for the XMLReader), but should be accessible + through JAXP which is the more modern and flexible access system. +

+ The purpose of the XMLReaderJDOMFactory Pillar is to give the SAXBuilder an + XMLReader instance (a SAX 2.0 parser). To get an XMLReader the + SAXBuilder delegates to the {@link org.jdom.input.sax.XMLReaderJDOMFactory} + by calling {@link org.jdom.input.sax.XMLReaderJDOMFactory#createXMLReader()} +

+ XMLReader instances can be created in a few different ways, and also they + can be set to perform the SAX parse in a number of different ways. The classes + in this package are designed to make it easier and faster to locate the XMLReader + that is suitable for the XML parsing you intend to do. At the same time, if the + parsing you intend to do is outside the normal bounds of how JDOM is used, you + still have the functionality to create a completely custom mechanism for setting + the XMLReader for SAXBuilder. +

+ There are two typical ways to specify and create an XMLReader + instance: using JAXP, and using the SAX2.0 API. If necessary you can also create + direct instances of XMLReader implementations using 'new' constructors, but + each SAX implementation has different class names for their SAX drivers so doing + raw constructors is not portable and not recommended. +

+ Where possible it is recommended that you use the JAXP mechanism for obtaining + XMLReaders because: +

    +
  • It is more 'modern'. +
  • It provides a more consistent interface to different SAX implementations +
  • It provides cleaner and more portable support for validating using the + {@link javax.xml.validation.Validator} mechanisms. +
  • It allows you to create differently-configured 'factories' that + create XMLReaders in a pre-specified format (SAX2.0 has a single global + factory that creates raw XMLReader instances that then need to be + re-configured for your task). +
+ +

JAXP Factories

+ JDOM exposes six factories that use JAXP to source XMLReaders. These factories + cover almost all conditions under which you would want a SAX parser: +
    +
  1. A simple non-validating SAX parser +
  2. A validating parser that uses the DOCTYPE references in the XML to validate + against. +
  3. A validating parser that uses the XML Schema (XSD) references embedded in + the XML to validate against. +
  4. A factory that uses a specific JAXP-based parser that can optionally + validate using the DTD DocType. +
  5. A validating parser that uses an external Schema (XML Schema, Relax NG, + etc.) to validate the XML against. +
  6. A special case of the Schema-validating factory that specialises in XML + Schema (XSD) validation and provides an easy way to create validating + XMLReaders based on single or multiple input XSD documents. +
+ The first three are all relatively simple, and are available as members of the + {@link org.jdom.input.sax.XMLReaders} enumeration. These members + are 'singletons' that can be used in a multi-threaded and concurrent way to + provide XMLReaders that are configured correctly for the respective behaviour. +

+ To parse with a specific (rather than the default) JAXP-based XML Parser + you can use the {@link org.jdom.input.sax.XMLReaderJAXPFactory}. This factory + can optionally be set to do DTD validation during the parse. +

+ To validate using an arbitrary external Schema you can use the + {@link org.jdom.input.sax.XMLReaderSchemaFactory} to create an instance for + the particular Schema you want to validate against. Because this requires an + input Schema it cannot be constructed as a singleton like the others. There + are constructors that allow you to use a specific (rather than the default) + JAXP-compatible parser. +

+ {@link org.jdom.input.sax.XMLReaderXSDFactory} is a special case of + XMLReaderSchemaFactory which internally uses an efficient mechanism to + compile Schema instances from one or many input XSD documents which can come + from multiple sources. There are constructors that allow you to use a specific + (rather than the default) JAXP-compatible parser. + +

SAX 2.0 Factory

+ + JDOM supports using the SAX 2.0 API for creating XMLReaders through using + either the 'default' SAX 2.0 implementation or a particular SAX Driver class. + SAX2.0 support is available by creating instances of the + {@link org.jdom.input.sax.XMLReaderSAX2Factory} class. +

+ It should be noted that it is preferable to use JAXP in JDOM because it is a + more flexible API that allows more portable code to be created. The JAXP + interface in JDOM is also able to support a wider array of functionality + out-of-the-box, but the same functionality would require SAX-implementation + specific configuration. +

+ JDOM does not provide a pre-configured way to do XML Schema validation through + the SAX2.0 API though. The SAX 2.0 API does not expose a convenient way to + configure different SAX implementations in a consistent way, so it is up to the + JDOM user to wrap the XMLReaderSAX2Factory in such a way that it reconfigures + the XMLReader to be appropriate for the task at hand. + +

Custom Factories

+ If your circumstances require it you can create your own implementation of the + {@link org.jdom.input.sax.XMLReaderJDOMFactory} to provide XMLReaders configured + as you like them. It will probably be best if you wrap an existing implementation + with your custom code though in order to get the best results fastest. +

+ Note that the existing JDOM implementations described above all set the + generated XMLReaders to be namespace-aware and to supply namespace-prefixes. + Custom implementations should also ensure that this is set unless you absolutely + know what you are doing. + + +

The SAXHandlerFactory Pillar

+ + The SAXHandler interprets the SAX calls and provides the information to the + JDOMFactory to create JDOM content. SAXBuilder creates a SAXHandler from the + {@link org.jdom.input.sax.SAXHandlerFactory} pillar. It is unusual for a JDOM + user to need to customise the manner in which this happens, but, in the event + that you do you can create a subclass of the SAXHandler class, and then create + an instance of the SAXHandlerFactory that returns new subclass instances. + This new factory can become a pillar in SAXBuilder and supply custom SAXHandlers + to the parse process. + + +

The JDOMFactory Pillar

+ + There are a couple of reasons for changing the JDOMFactory pillar in SAXBuilder. + The default JDOMFactory used is the {@link org.jdom.DefaultJDOMFactory}. This + factory validates the values being used to create JDOM content. There is also + the {@link org.jdom.UncheckedJDOMFactory} which does not validate the data, so + it should only be used if you are absolutely certain that your SAX source can + never provide illegal content. You may have other reasons for creating a custom + JDOMFactory such as if you need to create custom versions of JDOM Content like + a custom Element subclass. + +

Configuring the Pillars

+ + The JDOMFactory pillar is not configurable; you can only replace it entirely. + The other two pillars are configurable though, but you should inspect the + getters and setters on {@link org.jdom.input.SAXBuilder} to identify what can + (by default) be changed easily. Remember, if you have anything that needs to be + customised beyond what SAXBuilder offers you can always replace a pillar with a + custom implementation. + +

Execution Model

+ Once all the pillars are set and configured to your satisfaction you can 'build' + a JDOM Document from a source. The actual parse process consists of a 'setup', + 'parse', and 'reset' phase. +

+ The setup process involves obtaining an XMLReader from the XMLReaderJDOMFactory + and a SAXHandler (configured to use the JDOMFactory) from the SAXHandlerFactory. + These two instances are then configured to meet the settings specified on + SAXBuilder, and once configured they are 'compiled' in to a SAXBuilderEngine. +

+ The SAXBuilderEngine is a non-configurable 'embodiment' of the configuration of + the SAXBuilder when the engine was created, and it contains the entire + 'workflow' necessary to parse the input in to JDOM content. Further, it is a + guarantee that the XMLReader and SAXHandler instances in the SAXBuilderEngine + are never shared with any other engine or entity (assuming that the respective + factories never issue the same instances multiple times). There is no guarantee + made for the JDOMFactory being unique for each SAXBuilderEngine, but JDOMFactory + instances are supposed to be reentrant/thread-safe. +

+ The 'parse' phase starts once the setup phase is complete and the + SAXBuilderEngine has been created. The created engine is used to parse the input, + and the resulting Document is returned to the client. +

+ The 'reset' phase happens after the completion of the 'parse' phase, and it + resets the SAXBuilderEngine to its initial state, ready to process the next + parse request. + +

Parser Reuse

+ A large amount of the effort involved in parsing the document is actually the + creation of the XMLReader and the SAXHandler instances, as well as applying the + configuration to those instances (the 'setup' phase). +

+ JDOM2 uses the new SAXBuilderEngine to represent the state of the SAXBuilder + at the moment prior to the parse. SAXBuilder will then 'remember' and reuse this + exact SAXBuilderEngine until something changes in the SAXBuilder configuration. + As soon as the configuration changes in any way the engine will be forgotten and + a new one will be created when the SAXBuilder next parses a document. +

+ If you turn off parser reuse with + {@link org.jdom.input.SAXBuilder#setReuseParser(boolean)} then SAXBuilder will + immediately forget the engine, and it will also forget it after each build (i.e. + SAXBuilder will create a new SAXBuilderEngine each parse). +

+ It follows then that as long as you do not change the SAXBuilder configuration + then the SAXBuilder will always reuse the same SAXBuilderEngine. This is very + efficient because there is no configuration management between parses, and the + procedure completely eliminates the 'setup' component for all but the first + parse. + +

Parser Pooling

+ In order to facilitate Parser pooling it is useful to export the + SAXBuilderEngine as a stand-alone reusable parser. At any time you can call + {@link org.jdom.input.SAXBuilder#buildEngine()} and you can get a newly + created SAXBuilderEngine instance. The SAXBuilderEngine has the same 'build' + methods as SAXBuilder, and these are exposed as the + {@link org.jdom.input.sax.SAXEngine} interface. Both SAXBuilder and + SAXBuilderEngine implement the SAXEngine interface. Thus, if you use Parser + pools you can pool either the SAXBuilder or the SAXBuilderEngine in the same + pool. +

+ It is most likely though that what you will want to do is to create a single + SAXBuilder that represents the configuration you want, and then you can use this + single SAXBuilder to create multiple SAXEngines as you need them in the pool by + calling the buildEngine() method. + +

Examples

+ + Create a simple SAXBuilder and parse a document: +

+

+ SAXBuilder sb = new SAXBuilder();
+ Document doc = sb.build(new File("file.xml"));
+ 
+

+ Create a DTD validating SAXBuilder and parse a document: +

+

+ SAXBuilder sb = new SAXBuilder(XMLReaders.DTDVALIDATING);
+ Document doc = sb.build(new File("file.xml"));
+ 
+ Create an XSD (XML Schema) validating SAXBuilder using the XSD references inside + the XML document and parse a document: +

+

+ SAXBuilder sb = new SAXBuilder(XMLReaders.XSDVALIDATING);
+ Document doc = sb.build(new File("file.xml"));
+ 
+

+ Create an XSD (XML Schema) validating SAXBuilder the hard way (see the next + example for an easier way) using an external XSD and parse a document + (see {@link org.jdom.input.sax.XMLReaderSchemaFactory}): +

+

+ SchemaFactory schemafac =
+ SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+ Schema schema = schemafac.newSchema(new File("myschema.xsd"));
+ XMLReaderJDOMFactory factory = new XMLReaderSchemaFactory(schema);
+ SAXBuilder sb = new SAXBuilder(factory);
+ Document doc = sb.build(new File("file.xml"));
+ 
+

+ Create an XSD (XML Schema) validating SAXBuilder the easy way + (see {@link org.jdom.input.sax.XMLReaderXSDFactory}): +

+

+ File xsdfile = new File("myschema.xsd");
+ XMLReaderJDOMFactory factory = new XMLReaderXSDFactory(xsdfile);
+ SAXBuilder sb = new SAXBuilder(factory);
+ Document doc = sb.build(new File("file.xml"));
+ 
+ + */ +package org.jdom.input.sax; + diff --git a/core/src/java/org/jdom/input/stax/DTDParser.java b/core/src/java/org/jdom/input/stax/DTDParser.java new file mode 100644 index 0000000..b8d3abf --- /dev/null +++ b/core/src/java/org/jdom/input/stax/DTDParser.java @@ -0,0 +1,424 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.stax; + +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.jdom.DocType; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; + +/** + * Parses out key information from a single String representing a DOCTYPE + * declaration. StAX parsers supply a single string representing the DOCTYPE and + * this needs to be processed to get items like the SystemID, etc. Additionally + * it needs to be reformatted to create a standardised representation. + *

+ * The assumption is that the DTD is valid. + *

+ * We need to pull out 4 elements of data: + *

    + *
  1. The root element name + *
  2. The SystemID (if available) + *
  3. The PublicID (if available) + *
  4. The internal subset (if available) + *
+ * + * The internal-subset should be re-formatted to conform to the JDOM 'standard' + * where each declaration starts on a new line indented with 2 spaces. This + * 'standard' is defined by the way that JDOM formats the DTD declarations in the + * SAX parse process, which fires individual events for the content in the DTD. + *

+ * We can do this all with a well-structured regular expression, which is + * actually simpler than trying to fish out all the components ourselves.... + *

+ * + * @author Rolf Lear + * + */ +public class DTDParser { + + /* + * ======================================================================= + * + * READ THIS... + * + * + * This code works by using a reg-ex to parse a valid DTD document. + * The pattern is complicated (not as complicated as an actual parser). + * + * Because the pattern is complicated, this code creates a pattern 'database' + * and then 'pulls' patterns from the database to create the final regex. + * The database patterns are pulled to transform a pattern template into a + * final regular expression. This template is called the 'meta-pattern'. + * + * So, the pattern is not kept in its final form, but rather it is built + * up at class initialization time based on the meta-pattern, and the + * pattern database in the map. + * + * This is the final pattern: (broken over a few lines) + * + * [\s\r\n\t]*]+)([\s\r\n\t]+ + * ((SYSTEM[\s\r\n\t]+(('([^']*)')|("([^"]*)")))| + * (PUBLIC[\s\r\n\t]+(('([^']*)')|("([^"]*)"))([\s\r\n\t]+ + * (('([^']*)')|("([^"]*)")))?)))?([\s\r\n\t]*\[(.*)\])? + * [\s\r\n\t]*>[\s\r\n\t]* + * + * You will agree that it's simpler to build the pattern than to read it.... + * + * With the above in mind, you can easily follow the way the pattern is + * built as it is simply a repeating use of some of the base constructs. + * ======================================================================= + */ + + /** + * This is the meta-pattern. + *

+ *

    + *
  • Where you see ' os ' there is optional space. + *
  • Where you see ' name ' there is the element name. + *
  • Where you see ' ms ' there is mandatory space. + *
  • Where you see ' id ' there is some quoted identifier. + *
  • Where you see ' internal ' there is the internal subset. + *
+ * Anything else will become part of the final regex. + *

+ * Space (' ') was chosen for the token delimiter because it + * makes the meta-pattern easy to read. There are a couple of places in + * this expression where there are two ' ' together, and it is critical + * that it does not change because there will be missed token matches then. + */ + private static final String metapattern = + // The lead-in and the Element name + " os os "; + + /** + * This builds a substitution map containing the raw patterns for + * certain types of content we expect. + * @return The populated map. + */ + private static final HashMap populatePatterns() { + HashMap p = new HashMap(); + // The name is important to understand. The assumption is that the + // doctype is valid, hence it is easier to search for what the name is + // not, and not what it is. The name will be terminated with either + // white-space, [ or > + p.put("name", "[^ \\n\\r\\t\\[>]+"); // element name. + + // whitespace: S ::= (#x20 | #x9 | #xD | #xA)+ + p.put("ms", "[ \\n\\r\\t]+"); // mandatory whitespace. + p.put("os", "[ \\n\\r\\t]*"); // optional whitespace. + + // A quoted 'id'/"id" is anything except the quote + // we need to do parenthesis in this to get grouping to work. + // also need parenthesis to make the | or condition work + p.put("id", "(('([^']*)')|(\"([^\"]*)\"))"); // quoted id. + + // The internal subset is treated differently by the code, and the + // [ ] bracing around the internal subset is specified in the main regex + p.put("internal", ".*"); // internal subset. + return p; + } + + /** + * This method substitutes the simple tokens in the meta-pattern with + * the declared values in the map. + * @param map The map containing substitution tokens/patterns + * @param input The meta-pattern to do the substitutions on. + * @return The substituted pattern + */ + private static final Pattern buildPattern( + HashMap map, String input) { + // we are going to search for tokens. Each token is marked by a space. + // space was chosen because it makes the meta-pattern easy to read. + final Pattern search = Pattern.compile(" (\\w+) "); + final Matcher mat = search.matcher(input); + StringBuilder sb = new StringBuilder(); + int pos = 0; + while (mat.find()) { + String rep = map.get(mat.group(1)); +// we wrote this, it can't happen ;-). Live with a 'null' append. +// if (rep == null) { +// throw new IllegalArgumentException( +// "No definition of token '" + mat.group() + "'."); +// } + // can't use appendReplacement as we have to escape '\' chars. + // and Pattern.quote() does not help + // mat.appendReplacement(sb, rep); + sb.append(input.substring(pos, mat.start())); + sb.append(rep); + pos = mat.end(); + } + sb.append(input.substring(pos)); + return Pattern.compile(sb.toString(), Pattern.DOTALL); + } + + /** + * The following Pattern is the final result after + * parsing/tokenizing/substituting the meta-pattern. + */ + private static final Pattern pattern = + buildPattern(populatePatterns(), metapattern); + + /* + * This pattern relies on pattern grouping to easily pull the values from + * the Matcher. Look at the following to get an idea of the groups that + * come from the reg-ex + * + * 0 -> + * 1 -> root + * 2 -> SYSTEM "system" + * 3 -> SYSTEM "system" + * 4 -> SYSTEM "system" + * 5 -> "system" + * 6 -> null + * 7 -> null + * 8 -> "system" + * 9 -> system + * 10 -> null + * 11 -> null + * 12 -> null + * 13 -> null + * 14 -> null + * 15 -> null + * 16 -> null + * 17 -> null + * 18 -> null + * 19 -> null + * 20 -> null + * 21 -> null + * 22 -> [internal] + * 23 -> internal + * + * + * 0 -> + * 1 -> root + * 2 -> PUBLIC 'public' 'system' + * 3 -> PUBLIC 'public' 'system' + * 4 -> null + * 5 -> null + * 6 -> null + * 7 -> null + * 8 -> null + * 9 -> null + * 10 -> PUBLIC 'public' 'system' + * 11 -> 'public' + * 12 -> 'public' + * 13 -> public + * 14 -> null + * 15 -> null + * 16 -> 'system' + * 17 -> 'system' + * 18 -> 'system' + * 19 -> system + * 20 -> null + * 21 -> null + * 22 -> [internal] + * 23 -> internal + * + * + */ + + /** + * Looks in any number of matched groups for a value. Returns the first set + * value. The assumption is that, depending on the pattern matches, the + * value could be in a few different locations. + * @param mat The match that has succeeded + * @param groups The groups to check for a value. + * @return The first found value. + */ + private static final String getGroup(final Matcher mat, final int...groups) { + for (final int g : groups) { + final String s = mat.group(g); + if (s != null) { + return s; + } + } + return null; + } + + /** + * return true if the input character is one of the types recognized in the + * DTD spec. + * @param ch The char to check + * @return true if it is a space, tab, newline, or carriage-return. + */ + private static final boolean isWhite(char ch) { + return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; + } + + /** + * Reformat an internal subset.... Each declaration starts on an indented + * newline. + * @param internal the input DocType declaration as found in a StAX Reader. + * @return the formatted input. + */ + private static String formatInternal(String internal) { + StringBuilder sb = new StringBuilder(internal.length()); + char quote = ' '; + boolean white = true; + for (char ch : internal.toCharArray()) { + if (quote == ' ') { + // we are not in a quoted value... + if (isWhite(ch)) { + if (!white) { + // this will be the first whitespace. + // replace it with a single ' ' + sb.append(' '); + white = true; + } + // subsequent (unquoted) whitespace is ignored + } else { + if (ch == '\'' || ch == '"') { + // we are entering a quoted value. + quote = ch; + } else if (ch == '<') { + // we are starting some form of declaration. + sb.append(" "); + } + + if (ch == '>') { + // we are ending a declaration. + if (white) { + // the declaration ended with whitespace, which we + // remove. + sb.setCharAt(sb.length() - 1, ch); + } else { + // the declaration had no whitespace at the end. OK + sb.append(ch); + } + // all declarations end with a new-line. + sb.append('\n'); + // and subsequent lines start as trimmed whitespace. + white = true; + } else { + sb.append(ch); + white = false; + } + } + } else { + // we are in a quoted value... + if (ch == quote) { + //we are leaving the quoted value. + quote = ' '; + } + sb.append(ch); + } + } + return sb.toString(); + } + + /** + * Parse out a DOCTYPE declaration as supplied by the standard StAX + * readers. + *

+ * Using 'XML' terminology, this method assumes that the input is + * both 'well-formed' and 'valid'. The assumptions that this class makes + * ensure that the 'right thing' is done for valid content, but invalid + * content may or may not fail with a JDOMException. The behaviour of this + * method with invalid input is 'undefined'. + * + * @param input the input DOCTYPE string to parse. Must be valid. + * @param factory The JDOM factory to use to build the JDOM DocType. + * @return The input string as a DocType. + * @throws JDOMException if the DocType is not generated. + */ + public static DocType parse(final String input, final JDOMFactory factory) + throws JDOMException { + + // Match the input to the DOCTYPE pattern matcher. + final Matcher mat = pattern.matcher(input); + if (!mat.matches()) { + throw new JDOMException("Doctype input does not appear to be valid: " + input); + } + + // Get the four data components. + final String docemt = mat.group(1); + final String sysid = getGroup(mat, 7, 9, 19, 21); + final String pubid = getGroup(mat, 13, 15); + final String internal = getGroup(mat, 23); + + // Use the appropriate constructor for the DocType. + DocType dt = null; + if (pubid != null) { + dt = factory.docType(docemt, pubid, sysid); + } else if (sysid != null) { + dt = factory.docType(docemt, sysid); + } else { + dt = factory.docType(docemt); + } + // Set the internal subset, if any. + if (internal != null) { + dt.setInternalSubset(formatInternal(internal)); + } + return dt; + } + + /** + * Make instances 'impossible'. Everything is static. + */ + private DTDParser() { + // nothing, you are not allowed instances of this class. + } + +} diff --git a/core/src/java/org/jdom/input/stax/DefaultStAXFilter.java b/core/src/java/org/jdom/input/stax/DefaultStAXFilter.java new file mode 100644 index 0000000..2b22005 --- /dev/null +++ b/core/src/java/org/jdom/input/stax/DefaultStAXFilter.java @@ -0,0 +1,135 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.stax; + +import org.jdom.Namespace; + +/** + * This DefaultStAXFilter includes all content and prunes nothing. + *

+ * Override this class to make adjustments to get the results you need. + * + * @see StAXFilter + * + * @author Rolf Lear + */ +public class DefaultStAXFilter implements StAXFilter { + + @Override + public boolean includeDocType() { + return true; + } + + @Override + public boolean includeElement(final int depth, final String name, final Namespace ns) { + return true; + } + + @Override + public String includeComment(final int depth, final String comment) { + return comment; + } + + @Override + public boolean includeEntityRef(final int depth, final String name) { + return true; + } + + @Override + public String includeCDATA(final int depth, final String text) { + return text; + } + + @Override + public String includeText(final int depth, final String text) { + return text; + } + + @Override + public boolean includeProcessingInstruction(final int depth, final String target) { + return true; + } + + @Override + public boolean pruneElement(final int depth, final String name, final Namespace ns) { + return false; + } + + @Override + public String pruneComment(final int depth, final String comment) { + return comment; + } + + @Override + public boolean pruneEntityRef(final int depth, final String name) { + return false; + } + + @Override + public String pruneCDATA(final int depth, final String text) { + return text; + } + + @Override + public String pruneText(final int depth, final String text) { + return text; + } + + @Override + public boolean pruneProcessingInstruction(final int depth, final String target) { + return false; + } + +} diff --git a/core/src/java/org/jdom/input/stax/StAXFilter.java b/core/src/java/org/jdom/input/stax/StAXFilter.java new file mode 100644 index 0000000..ff0184a --- /dev/null +++ b/core/src/java/org/jdom/input/stax/StAXFilter.java @@ -0,0 +1,292 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.input.stax; + +import org.jdom.Namespace; + +/** + * In StAX Processing it is possible to read fragments of XML. JDOM supports + * reading JDOM Content from StAX Readers in fragments. JDOM users can influence + * the content that is processed by the return values in this interface. + *

+ * Using the StAXStreamBuilder or StAXEventBuilder you can parse a List of + * JDOM content by filtering that content with an instance of this filter. + *

+ * There are two significant states in which methods in this interface will be + * called: + *

    + *
  • We are not currently including any content, and we want to know whether + * the current StAX content should be included. + *
  • We are currently inside an Element that this filter has indicated should + * be included, but perhaps you want to prune some content. + *
+ * + * @author Rolf Lear + * + */ +public interface StAXFilter { + + /** + * The current event is a DocType event. + * @return true if the DocType should become a JDOM Fragment. + */ + public boolean includeDocType(); + + /** + * The current event is an Element event. + *

+ * If the return value of this call is true, then this Element will be + * processed as a JDOM fragment. You may then get calls to the prune* + * methods to determine whether child content of this Element should be + * pruned. + * + * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param name The XML tag name of this Element + * @param ns The Namespace of this Element + * @return true if the Element should become a JDOM Fragment. + */ + public boolean includeElement(int depth, String name, Namespace ns); + + /** + * The current event is a Comment event. + *

+ * A null return value will cause the Comment to be ignored, and a non-null + * return value will become the Comment's text. + *

+ * To include the comment as-is, do: + *
+ *

+	 * public String includeComment(int depth, String comment) {
+	 *     return comment;
+	 * }
+	 * 
+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param comment The Comment value + * @return null if you want to exclude this comment, or a non-null value + * which will become the new comment value. + */ + public String includeComment(int depth, String comment); + + /** + * The current event is an EntityRef event. + *

+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param name The EntityRef name + * @return true if you want to include this EntityRef. + */ + public boolean includeEntityRef(int depth, String name); + + /** + * The current event is a CDATA event. + *

+ * A null return value will cause the Comment to be ignored, and a non-null + * return value will become the CDATA's text. + *

+ * To include the CDATA as-is, do: + *
+ *

+	 * public String includeCDATA(int depth, String text) {
+	 *     return text;
+	 * }
+	 * 
+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param text The CDATA text value + * @return null if you want to exclude this CDATA, or a non-null value + * which will become the new CDATA text value. + */ + public String includeCDATA(int depth, String text); + + /** + * The current event is a TEXT event. + *

+ * A null return value will cause the Comment to be ignored, and a non-null + * return value will become the Text's text. + *

+ * To include the Text as-is, do: + *
+ *

+	 * public String includeText(int depth, String text) {
+	 *     return text;
+	 * }
+	 * 
+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param text The Text value + * @return null if you want to exclude this Text, or a non-null value + * which will become the new Text value. + */ + public String includeText(int depth, String text); + + /** + * The current event is a ProcessingInstruction event. + *

+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param target The ProcessingInstruction Target value + * @return true if you want to include this ProcessingInstruction. + */ + public boolean includeProcessingInstruction(int depth, String target); + + /** + * An Element is being included, and this is a child Element event of the + * included parent Element. Should this Child Element be pruned from the + * parent fragment? + * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param name The XML tag name of this child Element + * @param ns The Namespace of this child Element + * @return true if the child Element should be excluded. + */ + public boolean pruneElement(int depth, String name, Namespace ns); + + + /** + * An Element is being included, and this is a child Comment event of the + * included parent Element. Should this child Comment be pruned from the + * parent fragment? + *

+ * A non-null return value will become the Comment value. Return null to + * skip the Comment. + *

+ * To include the Comment as-is, do: + *
+ *

+	 * public String pruneComment(int depth, String comment) {
+	 *     return comment;
+	 * }
+	 * 
+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param comment The Comment value + * @return null if you want to exclude this Comment, or a non-null value + * which will become the new Comment value. + */ + public String pruneComment(int depth, String comment); + + /** + * An Element is being included, and this is a child EntityRef event of the + * included parent Element. Should this child EntityRef be pruned from the + * parent fragment? + *

+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param name The EntityRef name + * @return true if you want to exclude this EntityRef. + */ + public boolean pruneEntityRef(int depth, String name); + + /** + * An Element is being included, and this is a child CDATA event of the + * included parent Element. Should this child CDATA be pruned from the + * parent fragment? + *

+ * A non-null return value will become the CDATA text. Return null to skip + * the CDATA. + *

+ * To include the CDATA as-is, do: + *
+ *

+	 * public String pruneCDATA(int depth, String text) {
+	 *     return text;
+	 * }
+	 * 
+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param text The CDATA text value + * @return null if you want to exclude this CDATA, or a non-null value + * which will become the new CDATA text value. + */ + public String pruneCDATA(int depth, String text); + + /** + * An Element is being included, and this is a child Text event of the + * included parent Element. Should this child Text be pruned from the + * parent fragment? + *

+ * A non-null return value will become the Text. Return null to skip + * the Text. + *

+ * To include the Text as-is, do: + *
+ *

+	 * public String pruneText(int depth, String text) {
+	 *     return text;
+	 * }
+	 * 
+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param text The Text value + * @return null if you want to exclude this Text, or a non-null value + * which will become the new Text value. + */ + public String pruneText(int depth, String text); + + /** + * An Element is being included, and this is a child ProcessingInstruction + * event of the included parent Element. Should this ProcessingInstruction + * be pruned from the parent fragment? + *

+ * @param depth The depth of this content from the document root + * (the root Element is at depth 0) + * @param target The ProcessingInstruction Target value + * @return true if you want to exclude this ProcessingInstruction. + */ + public boolean pruneProcessingInstruction(int depth, String target); + +} diff --git a/core/src/java/org/jdom/input/stax/package-info.java b/core/src/java/org/jdom/input/stax/package-info.java new file mode 100644 index 0000000..ba68c96 --- /dev/null +++ b/core/src/java/org/jdom/input/stax/package-info.java @@ -0,0 +1,60 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +/** + Support classes for building JDOM documents and content using StAX readers. + + */ +package org.jdom.input.stax; + diff --git a/core/src/java/org/jdom/internal/ArrayCopy.java b/core/src/java/org/jdom/internal/ArrayCopy.java new file mode 100644 index 0000000..1cfce66 --- /dev/null +++ b/core/src/java/org/jdom/internal/ArrayCopy.java @@ -0,0 +1,143 @@ +/*-- + + Copyright (C) 2011 - 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.internal; + +import java.lang.reflect.Array; + +/** + * The copyOf methods on java.util.Arrays are introduced in Java6. Need an + * alternative to support Java5. + * + * @author Rolf Lear + * + */ +public final class ArrayCopy { + + private ArrayCopy() { + // inaccessible constructor. + } + + /** + * Arrays.copyOf(...) is a Java6 thing. This is a replacement. + * @param The generic type of the array we are copying. + * @param source the source array. + * @param len the length of the new array copy. + * @return a new array that has the same elements as the source. + */ + public static final E[] copyOf(final E[] source, final int len) { + @SuppressWarnings("unchecked") + final E[] dest = (E[])Array.newInstance(source.getClass().getComponentType(), len); + System.arraycopy(source, 0, dest, 0, len < source.length ? len : source.length); + return dest; + } + + /** + * Arrays.copyOf(...) is a Java6 thing. This is a replacement. + * @param The generic type of the array we are copying. + * @param source the source array. + * @param from the start point of the copy (inclusive). + * @param to the end point of the copy (exclusive). + * @return a new array that has the same elements as the source. + */ + public static final E[] copyOfRange(final E[] source, final int from, int to) { + final int len = to - from; + if (len < 0) { + throw new IllegalArgumentException("From(" + from + ") > To (" + to + ")"); + } + @SuppressWarnings("unchecked") + final E[] dest = (E[])Array.newInstance(source.getClass().getComponentType(), len); + final int tocopy = from + len > source.length ? source.length - from : len; + System.arraycopy(source, from, dest, 0, tocopy); + return dest; + } + + /** + * Arrays.copyOf(...) is a Java6 thing. This is a replacement. + * @param source the source array. + * @param len the length of the new array copy. + * @return a new array that has the same elements as the source. + */ + public static final char[] copyOf(final char[] source, final int len) { + final char[] dest = new char[len]; + System.arraycopy(source, 0, dest, 0, len < source.length ? len : source.length); + return dest; + } + + /** + * Arrays.copyOf(...) is a Java6 thing. This is a replacement. + * @param source the source array. + * @param len the length of the new array copy. + * @return a new array that has the same elements as the source. + */ + public static final int[] copyOf(final int[] source, final int len) { + final int[] dest = new int[len]; + System.arraycopy(source, 0, dest, 0, len < source.length ? len : source.length); + return dest; + } + + /** + * Arrays.copyOf(...) is a Java6 thing. This is a replacement. + * @param source the source array. + * @param len the length of the new array copy. + * @return a new array that has the same elements as the source. + */ + public static final boolean[] copyOf(final boolean[] source, final int len) { + final boolean[] dest = new boolean[len]; + System.arraycopy(source, 0, dest, 0, len < source.length ? len : source.length); + return dest; + } + + +} diff --git a/core/src/java/org/jdom/internal/ReflectionConstructor.java b/core/src/java/org/jdom/internal/ReflectionConstructor.java new file mode 100644 index 0000000..8dfd083 --- /dev/null +++ b/core/src/java/org/jdom/internal/ReflectionConstructor.java @@ -0,0 +1,102 @@ +/*-- + + Copyright (C) 2011 - 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.internal; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * Utility class that handles constructing a class using reflection, and a + * no-argument 'default' constructor. + * + * @author Rolf Lear + * + */ +public class ReflectionConstructor { + + /** + * Construct a new instance of the named class, and ensure it is cast + * to the type specified as the targetclass. + * @param The generic type of the returned value. + * @param classname The class name of the instance to create. + * @param targetclass The return type of the created instance + * @return an instantiated class + * @throws IllegalArgumentException if there is a problem locating the class instance. + * @throws IllegalStateException if there is a problem instantiating a class instance. + */ + public static final E construct(String classname, Class targetclass) { + try { + Class sclass = Class.forName(classname); + if (!targetclass.isAssignableFrom(sclass)) { + throw new ClassCastException("Class '" + classname + "' is not assignable to '" + targetclass.getName() + "'."); + } + Constructor constructor = sclass.getConstructor(); + Object o = constructor.newInstance(); + return targetclass.cast(o); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Unable to locate class '" + classname + "'.", e); + } catch (NoSuchMethodException e) { + throw new IllegalArgumentException("Unable to locate class no-arg constructor '" + classname + "'.", e); + } catch (SecurityException e) { + throw new IllegalStateException("Unable to access class constructor '" + classname + "'.", e); + } catch (IllegalAccessException e) { + throw new IllegalStateException("Unable to access class constructor '" + classname + "'.", e); + } catch (InstantiationException e) { + throw new IllegalStateException("Unable to instantiate class '" + classname + "'.", e); + } catch (InvocationTargetException e) { + throw new IllegalStateException("Unable to call class constructor '" + classname + "'.", e); + } + } +} diff --git a/core/src/java/org/jdom/internal/SystemProperty.java b/core/src/java/org/jdom/internal/SystemProperty.java new file mode 100644 index 0000000..87a6ec2 --- /dev/null +++ b/core/src/java/org/jdom/internal/SystemProperty.java @@ -0,0 +1,81 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.internal; + +/** + * System.getProperty(...) requires security permissions in Applets, and some + * other cases and this class contains static methods that allow the security + * exceptions to fail silently. + * + * @author Rolf Lear + * + */ +public final class SystemProperty { + + /** + * Query the System properties for a particular property. If the property + * is not set, or not accessible, it returns the def value. + * @param property The property to get + * @param def The value to return if the property is not accessible or not set. + * @return the appropriate property value. + */ + public static final String get(final String property, final String def) { + try { + return System.getProperty(property, def); + } catch (SecurityException se) { + return def; + } + } +} diff --git a/core/src/java/org/jdom/internal/package.html b/core/src/java/org/jdom/internal/package.html new file mode 100644 index 0000000..6423ae6 --- /dev/null +++ b/core/src/java/org/jdom/internal/package.html @@ -0,0 +1,4 @@ + + Classes that implement reusable functionality that are not part of the + official JDOM API, but are used by many of the JDOM classes. + diff --git a/core/src/java/org/jdom/located/Located.java b/core/src/java/org/jdom/located/Located.java new file mode 100644 index 0000000..99c6f34 --- /dev/null +++ b/core/src/java/org/jdom/located/Located.java @@ -0,0 +1,101 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +/** + * Implementations of this class know about their location (line and column). + *

+ * While it would seem intuitive that this represents the location where the + * content starts, in fact, if the data is populated by a SAX parser the line + * and column values represent the end of the SAX + * event. + *

+ * SAX parsers may vary, but it typically means the + * character after the last character for Text and CDATA values, the character + * after EntityRef, Comment, and ProcessingInstruction data, and the character + * after the opening tag for Element content. For DocType content, it appears + * that Xerces is inconsistent in the location, with the location being set at + * what appears to be the start of the internal subset data (if any). + *

+ * Finally, remember that the column value counts characters, and thus, if you + * have tab-indented values, the tab counts as a single character (regardless of + * how much it indents). + * + * @author Rolf Lear + * + */ +public interface Located { + /** + * Get the line number + * @return the line number + */ + public int getLine(); + /** + * Get the column (character on the line). + * @return the column + */ + public int getColumn(); + + /** + * Set the line number + * @param line the line. + */ + public void setLine(int line); + /** + * Set the column (character on the line). + * @param col The column + */ + public void setColumn(int col); +} diff --git a/core/src/java/org/jdom/located/LocatedCDATA.java b/core/src/java/org/jdom/located/LocatedCDATA.java new file mode 100644 index 0000000..b9f6a4a --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedCDATA.java @@ -0,0 +1,113 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import org.jdom.CDATA; +import org.jdom.IllegalDataException; +import org.jdom.Text; + +/** + * An XML CDATA section. Represents character-based content within an XML + * document that should be output within special CDATA tags. Semantically it's + * identical to a simple {@link Text} object, but output behavior is different. + * CDATA makes no guarantees about the underlying textual representation of + * character data, but does expose that data as a Java String. + * + * @author Rolf Lear + */ +public class LocatedCDATA extends CDATA implements Located { + + /** + * This constructor creates a new LocatedCDATA node, with the + * supplied string value as it's character content. + * + * @param str the node's character content. + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + */ + public LocatedCDATA(String str) { + super(str); + } + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + + +} diff --git a/core/src/java/org/jdom/located/LocatedComment.java b/core/src/java/org/jdom/located/LocatedComment.java new file mode 100644 index 0000000..b70cc5e --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedComment.java @@ -0,0 +1,104 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import org.jdom.Comment; + +/** + * An XML comment. Methods allow the user to get and set the text of the + * comment. + * + * @author Rolf Lear + */ +public class LocatedComment extends Comment implements Located { + + /** + * This creates the comment with the supplied text. + * + * @param text String content of comment. + */ + public LocatedComment(String text) { + super(text); + } + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + + +} diff --git a/core/src/java/org/jdom/located/LocatedDocType.java b/core/src/java/org/jdom/located/LocatedDocType.java new file mode 100644 index 0000000..5e49f5e --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedDocType.java @@ -0,0 +1,147 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import org.jdom.DocType; +import org.jdom.IllegalDataException; +import org.jdom.IllegalNameException; + +/** + * An XML DOCTYPE declaration. Method allow the user to get and set the + * root element name, public id, and system id. + * + * @author Rolf Lear + */ +public class LocatedDocType extends DocType implements Located { + + /** + * This will create the DocType with + * the specified element name and a reference to an + * external DTD. + * + * @param elementName String name of + * element being constrained. + * @param publicID String public ID of + * referenced DTD + * @param systemID String system ID of + * referenced DTD + * @throws IllegalDataException if the given system ID is not a legal + * system literal or the public ID is not a legal public ID. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public LocatedDocType(String elementName, String publicID, String systemID) { + super(elementName, publicID, systemID); + } + + /** + * This will create the DocType with + * the specified element name and reference to an + * external DTD. + * + * @param elementName String name of + * element being constrained. + * @param systemID String system ID of + * referenced DTD + * @throws IllegalDataException if the given system ID is not a legal + * system literal. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public LocatedDocType(String elementName, String systemID) { + super(elementName, systemID); + } + + /** + * This will create the DocType with + * the specified element name + * + * @param elementName String name of + * element being constrained. + * @throws IllegalNameException if the given root element name is not a + * legal XML element name. + */ + public LocatedDocType(String elementName) { + super(elementName); + } + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + +} diff --git a/core/src/java/org/jdom/located/LocatedElement.java b/core/src/java/org/jdom/located/LocatedElement.java new file mode 100644 index 0000000..b63fcb7 --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedElement.java @@ -0,0 +1,152 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import org.jdom.Element; +import org.jdom.IllegalNameException; +import org.jdom.Namespace; + +/** + * This Element specialization contains the location information as parsed. + * + * @author Rolf Lear + * + */ +public class LocatedElement extends Element implements Located { + + /** + * Creates a new element with the supplied (local) name and namespace. If + * the provided namespace is null, the element will have no namespace. + * + * @param name local name of the element + * @param namespace namespace for the element + * @throws IllegalNameException if the given name is illegal as an element + * name + */ + public LocatedElement(final String name, final Namespace namespace) { + super(name, namespace); + } + + /** + * Create a new element with the supplied (local) name and no namespace. + * + * @param name local name of the element + * @throws IllegalNameException if the given name is illegal as an element + * name. + */ + public LocatedElement(final String name) { + super(name); + } + + /** + * Creates a new element with the supplied (local) name and a namespace + * given by a URI. The element will be put into the unprefixed (default) + * namespace. + * + * @param name name of the element + * @param uri namespace URI for the element + * @throws IllegalNameException if the given name is illegal as an element + * name or the given URI is illegal as a + * namespace URI + */ + public LocatedElement(final String name, final String uri) { + super(name, uri); + } + + /** + * Creates a new element with the supplied (local) name and a namespace + * given by the supplied prefix and URI combination. + * + * @param name local name of the element + * @param prefix namespace prefix + * @param uri namespace URI for the element + * @throws IllegalNameException if the given name is illegal as an element + * name, the given prefix is illegal as a + * namespace prefix, or the given URI is + * illegal as a namespace URI + */ + public LocatedElement(final String name, final String prefix, final String uri) { + super(name, prefix, uri); + } + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + + +} diff --git a/core/src/java/org/jdom/located/LocatedEntityRef.java b/core/src/java/org/jdom/located/LocatedEntityRef.java new file mode 100644 index 0000000..0b8c306 --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedEntityRef.java @@ -0,0 +1,139 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import org.jdom.EntityRef; +import org.jdom.IllegalDataException; +import org.jdom.IllegalNameException; + +/** + * An XML entity reference. Methods allow the user to manage its name, public + * id, and system id. + * + * @author Rolf Lear + */ +public class LocatedEntityRef extends EntityRef implements Located { + + /** + * This will create a new EntityRef with the supplied name. + * + * @param name String name of element. + * @throws IllegalNameException if the given name is not a legal + * XML name. + */ + public LocatedEntityRef(String name) { + super(name); + } + + /** + * This will create a new EntityRef + * with the supplied name and system id. + * + * @param name String name of element. + * @param systemID system id of the entity reference being constructed + * @throws IllegalNameException if the given name is not a legal + * XML name. + * @throws IllegalDataException if the given system ID is not a legal + * system literal. + */ + public LocatedEntityRef(String name, String systemID) { + super(name, systemID); + } + + /** + * This will create a new EntityRef + * with the supplied name, public id, and system id. + * + * @param name String name of element. + * @param publicID public id of the entity reference being constructed + * @param systemID system id of the entity reference being constructed + * @throws IllegalDataException if the given system ID is not a legal + * system literal or the the given public ID is not a + * legal public ID + * @throws IllegalNameException if the given name is not a legal + * XML name. + */ + public LocatedEntityRef(String name, String publicID, String systemID) { + super(name, publicID, systemID); + } + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + +} diff --git a/core/src/java/org/jdom/located/LocatedJDOMFactory.java b/core/src/java/org/jdom/located/LocatedJDOMFactory.java new file mode 100644 index 0000000..09255b2 --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedJDOMFactory.java @@ -0,0 +1,220 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import java.util.Map; + +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; + +/** + * All Content instances (Element, Comment, CDATA, DocType, Text, EntityRef, + * and ProcessingInstruction) will implement {@link Located}, and will + * have the values set appropriately. + *

+ * You can set an instance of this LocatedJDOMFactory as the factory for a + * SAXBuilder, and the JDOM document produced will have the SAX Location data + * embedded. Note though, that SAX Location data indicates the position of the + * end of the SAX Event. + * + * @author Rolf Lear + * + */ +public class LocatedJDOMFactory extends DefaultJDOMFactory { + + @Override + public CDATA cdata(int line, int col, String text) { + final LocatedCDATA ret = new LocatedCDATA(text); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public Text text(int line, int col, String text) { + final LocatedText ret = new LocatedText(text); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public Comment comment(int line, int col, String text) { + final LocatedComment ret = new LocatedComment(text); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public DocType docType(int line, int col, String elementName, + String publicID, String systemID) { + final LocatedDocType ret = new LocatedDocType(elementName, publicID, systemID); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public DocType docType(int line, int col, String elementName, + String systemID) { + final LocatedDocType ret = new LocatedDocType(elementName, systemID); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public DocType docType(int line, int col, String elementName) { + final LocatedDocType ret = new LocatedDocType(elementName); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public Element element(int line, int col, String name, Namespace namespace) { + final LocatedElement ret = new LocatedElement(name, namespace); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public Element element(int line, int col, String name) { + final LocatedElement ret = new LocatedElement(name); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public Element element(int line, int col, String name, String uri) { + final LocatedElement ret = new LocatedElement(name, uri); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public Element element(int line, int col, String name, String prefix, + String uri) { + final LocatedElement ret = new LocatedElement(name, prefix, uri); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public ProcessingInstruction processingInstruction(int line, int col, + String target) { + final LocatedProcessingInstruction ret = new LocatedProcessingInstruction(target); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public ProcessingInstruction processingInstruction(int line, int col, + String target, Map data) { + final LocatedProcessingInstruction ret = new LocatedProcessingInstruction(target, data); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public ProcessingInstruction processingInstruction(int line, int col, + String target, String data) { + final LocatedProcessingInstruction ret = new LocatedProcessingInstruction(target, data); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public EntityRef entityRef(int line, int col, String name) { + final LocatedEntityRef ret = new LocatedEntityRef(name); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public EntityRef entityRef(int line, int col, String name, String publicID, + String systemID) { + final LocatedEntityRef ret = new LocatedEntityRef(name, publicID, systemID); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + @Override + public EntityRef entityRef(int line, int col, String name, String systemID) { + final LocatedEntityRef ret = new LocatedEntityRef(name, systemID); + ret.setLine(line); + ret.setColumn(col); + return ret; + } + + +} diff --git a/core/src/java/org/jdom/located/LocatedProcessingInstruction.java b/core/src/java/org/jdom/located/LocatedProcessingInstruction.java new file mode 100644 index 0000000..cbdda10 --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedProcessingInstruction.java @@ -0,0 +1,140 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import java.util.Map; + +import org.jdom.IllegalTargetException; +import org.jdom.ProcessingInstruction; + +/** + * An XML processing instruction. Methods allow the user to obtain the target of + * the PI as well as its data. The data can always be accessed as a String or, + * if the data appears akin to an attribute list, can be retrieved as name/value + * pairs. + * + * @author Rolf Lear + */ +public class LocatedProcessingInstruction extends ProcessingInstruction implements + Located { + + /** + * This will create a new ProcessingInstruction + * with the specified target. + * + * @param target String target of PI. + * @throws IllegalTargetException if the given target is illegal + * as a processing instruction name. + */ + public LocatedProcessingInstruction(String target) { + super(target); + } + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param target String target of PI. + * @param data Map data for PI, in + * name/value pairs + * @throws IllegalTargetException if the given target is illegal + * as a processing instruction name. + */ + public LocatedProcessingInstruction(String target, Map data) { + super(target, data); + } + + /** + * This will create a new ProcessingInstruction + * with the specified target and data. + * + * @param target String target of PI. + * @param data String data for PI. + * @throws IllegalTargetException if the given target is illegal + * as a processing instruction name. + */ + public LocatedProcessingInstruction(String target, String data) { + super(target, data); + } + + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + +} diff --git a/core/src/java/org/jdom/located/LocatedText.java b/core/src/java/org/jdom/located/LocatedText.java new file mode 100644 index 0000000..d982986 --- /dev/null +++ b/core/src/java/org/jdom/located/LocatedText.java @@ -0,0 +1,110 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.located; + +import org.jdom.IllegalDataException; +import org.jdom.Text; + +/** + * An XML character sequence. Provides a modular, parentable method of + * representing text. Text makes no guarantees about the underlying textual + * representation of character data, but does expose that data as a Java String. + * + * @author Rolf Lear + */ +public class LocatedText extends Text implements Located { + + /** + * This constructor creates a new Text node, with the + * supplied string value as it's character content. + * + * @param str the node's character content. + * @throws IllegalDataException if str contains an + * illegal character such as a vertical tab (as determined + * by {@link org.jdom.Verifier#checkCharacterData}) + */ + public LocatedText(String str) { + super(str); + } + + /** + * JDOM2 Serialization. In this case, DocType is simple. + */ + private static final long serialVersionUID = 200L; + + private int line, col; + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return col; + } + + @Override + public void setLine(int line) { + this.line = line; + } + + @Override + public void setColumn(int col) { + this.col = col; + } + + +} diff --git a/core/src/java/org/jdom/located/package.html b/core/src/java/org/jdom/located/package.html new file mode 100644 index 0000000..4fe4a3a --- /dev/null +++ b/core/src/java/org/jdom/located/package.html @@ -0,0 +1,12 @@ + + +Extended JDOM Content Classes that contain location coordinates. The coordinates +are accessible using the Located interface which has getters and +setters for the line and column details. +

+In addition, there is the LocatedJDOMFactory which can be used to +create the Located-aware Content. The +LocatedJDOMFactory can be used by a SAXBuilder to +preserve the location data on the Content. + + diff --git a/core/src/java/org/jdom/output/DOMOutputter.java b/core/src/java/org/jdom/output/DOMOutputter.java new file mode 100644 index 0000000..89e0cc4 --- /dev/null +++ b/core/src/java/org/jdom/output/DOMOutputter.java @@ -0,0 +1,614 @@ +/*-- + + Copyright (C) 2000-2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.adapters.DOMAdapter; +import org.jdom.adapters.JAXPDOMAdapter; +import org.jdom.internal.ReflectionConstructor; +import org.jdom.output.support.AbstractDOMOutputProcessor; +import org.jdom.output.support.DOMOutputProcessor; + +/** + * Outputs a JDOM {@link org.jdom.Document org.jdom.Document} as a DOM + * {@link org.w3c.dom.Document org.w3c.dom.Document}. Also provides methods to + * output other types of JDOM Content in the equivalent DOM nodes. + *

+ * There are two versions of most functions, one that creates an independent DOM + * node using the DOMAdapter to create a new org.w3c.dom.Document. The other + * version creates the new DOM Nodes using the supplied org.w3c.dom.Document + * instance. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Matthew Merlo + * @author Dan Schaffer + * @author Yusuf Goolamabbas + * @author Bradley S. Huffman + * @author Rolf lear + */ +public class DOMOutputter { + + /** + * Create a final/concrete instance of the AbstractDOMOutputProcessor. + * Making it final improves performance. + * + * @author Rolf Lear + */ + private static final class DefaultDOMOutputProcessor extends + AbstractDOMOutputProcessor { + // add nothing except make it final. + } + + /** Default adapter class */ + private static final DOMAdapter DEFAULT_ADAPTER = new JAXPDOMAdapter(); + + private static final DOMOutputProcessor DEFAULT_PROCESSOR = new DefaultDOMOutputProcessor(); + + /** Adapter to use for interfacing with the DOM implementation */ + private DOMAdapter adapter; + + private Format format; + + private DOMOutputProcessor processor; + + /** + * This creates a new DOMOutputter which will attempt to first locate a DOM + * implementation to use via JAXP, and if JAXP does not exist or there's a + * problem, will fall back to the default parser. + */ + public DOMOutputter() { + this(null, null, null); + } + + /** + * This creates a new DOMOutputter which uses the defalt (JAXP) DOM + * implementation but with a custom processor. + * + * @param processor + * the custom processor to use. + * @since JDOM2 + */ + public DOMOutputter(DOMOutputProcessor processor) { + this(null, null, processor); + } + + /** + * The complete constructor for specifying a custom DOMAdaptor, Format, and + * DOMOutputProcessor. + * + * @param adapter + * The adapter to use to create the base Document instance (null + * implies the default). + * @param format + * The output Format to use (null implies the default). + * @param processor + * The custom mechanism for doing the output (null implies the + * default). + * @since JDOM2 + */ + public DOMOutputter(DOMAdapter adapter, Format format, + DOMOutputProcessor processor) { + this.adapter = adapter == null ? DEFAULT_ADAPTER : adapter; + this.format = format == null ? Format.getRawFormat() : format; + this.processor = processor == null ? DEFAULT_PROCESSOR : processor; + } + + /** + * This creates a new DOMOutputter using the specified DOMAdapter + * implementation as a way to choose the underlying parser. + * + * @param adapterClass + * String name of class to use for DOM output + * @throws IllegalArgumentException + * if the adapter could not be instantiated. (it should be + * JDOMException, but that would require a change to this deprecated + * method's signature... + * @deprecated use {@link DOMOutputter#DOMOutputter(DOMAdapter)} instead. + */ + @Deprecated + public DOMOutputter(String adapterClass) { + if (adapterClass == null) { + adapter = DEFAULT_ADAPTER; + } else { + adapter = ReflectionConstructor.construct(adapterClass, + DOMAdapter.class); + } + } + + /** + * This creates a new DOMOutputter using the specified DOMAdapter + * implementation as a way to choose the underlying parser. + *

+ * If the specified adapter is not thread-safe then the user should ensure + * that the adapter instance is never shared between multiple DOMOutputters. + * The default DOMAdapter {@link JAXPDOMAdapter} is thread-safe. + * + * @param adapter + * the DOMAdapter instance to use for creating the base + * org.w3c.dom.Document Specify the null value to get the default + * adapter. + * @since JDOM2 + */ + public DOMOutputter(DOMAdapter adapter) { + this.adapter = adapter == null ? DEFAULT_ADAPTER : adapter; + } + + /** + * Get the DOMAdapter currently set for this DOMOutputter. + * + * @return the current DOMAdapter. + * @since JDOM2 + */ + public DOMAdapter getDOMAdapter() { + return adapter; + } + + /** + * Set the DOMAdapter currently set for this DOMOutputter. + * + * @param adapter + * the new DOMAdapter to use (null implies the default). + * @since JDOM2 + */ + public void setDOMAdapter(DOMAdapter adapter) { + this.adapter = adapter == null ? DEFAULT_ADAPTER : adapter; + } + + /** + * Get the Format instance currently used by this DOMOutputter. + * + * @return the current Format instance + * @since JDOM2 + */ + public Format getFormat() { + return format; + } + + /** + * Set a new Format instance for this DOMOutputter + * + * @param format + * the new Format instance to use (null implies the default) + * @since JDOM2 + */ + public void setFormat(Format format) { + this.format = format == null ? Format.getRawFormat() : format; + } + + /** + * Get the current DOMOutputProcessor + * + * @return the current DOMOutputProcessor + * @since JDOM2 + */ + public DOMOutputProcessor getDOMOutputProcessor() { + return processor; + } + + /** + * Set a new DOMOutputProcessor for this DOMOutputter. + * + * @param processor + * the new processor to set (null implies the default) + * @since JDOM2 + */ + public void setDOMOutputProcessor(DOMOutputProcessor processor) { + this.processor = processor == null ? DEFAULT_PROCESSOR : processor; + } + + /** + * Controls how NO_NAMESPACE nodes are handled. If true the outputter always + * creates a namespace aware DOM. + * + * @param flag + * true to force NamespaceAware + * @deprecated All DOMOutputters are now always NamespaceAware. + */ + @Deprecated + public void setForceNamespaceAware(boolean flag) { + // do nothing + } + + /** + * Returns whether DOMs will be constructed with namespaces even when the + * source document has elements all in the empty namespace. + * + * @return the forceNamespaceAware flag value + * @deprecated All DOMOutputters are always NamesapceAware. Always true. + */ + @Deprecated + public boolean getForceNamespaceAware() { + return true; + } + + /** + * This converts the JDOM Document parameter to a DOM Document, + * returning the DOM version. The DOM implementation is the one supplied by + * the current DOMAdapter. + * + * @param document + * Document to output. + * @return an org.w3c.dom.Document version + * @throws JDOMException + * if output failed. + */ + public org.w3c.dom.Document output(Document document) throws JDOMException { + return processor.process(adapter.createDocument(document.getDocType()), + format, document); + } + + /** + * This converts the JDOM DocType parameter to a DOM DocumentType, + * returning the DOM version. The DOM implementation is the one supplied by + * the current DOMAdapter. + *

+ * Unlike the other DOM Nodes, you cannot use a DOM Document to simply create a DOM DocumentType Node, + * it has to be created at the same time as the DOM Document instance. As a result, there is no + * version of this method that takes a DOM Document instance. + * + * @param doctype + * DocType to output. + * @return an org.w3c.dom.DocumentType version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.DocumentType output(DocType doctype) throws JDOMException { + return adapter.createDocument(doctype).getDoctype(); + } + + /** + * This converts the JDOM Element parameter to a DOM Element, + * returning the DOM version. The DOM Node will be linked to an independent + * DOM Document instance supplied by the current DOMAdapter + * + * @param element + * Element to output. + * @return an org.w3c.dom.Element version + * @throws JDOMException + * if output failed. + */ + public org.w3c.dom.Element output(Element element) throws JDOMException { + return processor.process(adapter.createDocument(), format, element); + } + + /** + * This converts the JDOM Text parameter to a DOM Text Node, + * returning the DOM version. The DOM Node will be linked to an independent + * DOM Document instance supplied by the current DOMAdapter + * + * @param text + * Text to output. + * @return an org.w3c.dom.Text version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Text output(Text text) throws JDOMException { + return processor.process(adapter.createDocument(), format, text); + } + + /** + * This converts the JDOM CDATA parameter to a DOM CDATASection + * Node, returning the DOM version. The DOM Node will be linked to an + * independent DOM Document instance supplied by the current DOMAdapter + * + * @param cdata + * CDATA to output. + * @return an org.w3c.dom.CDATASection version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.CDATASection output(CDATA cdata) throws JDOMException { + return processor.process(adapter.createDocument(), format, cdata); + } + + /** + * This converts the JDOM ProcessingInstruction parameter to a + * DOM ProcessingInstruction, returning the DOM version. The DOM Node will + * be linked to an independent DOM Document instance supplied by the current + * DOMAdapter + * + * @param pi + * ProcessingInstruction to output. + * @return an org.w3c.dom.Element version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.ProcessingInstruction output(ProcessingInstruction pi) + throws JDOMException { + return processor.process(adapter.createDocument(), format, pi); + } + + /** + * This converts the JDOM ProcessingInstruction parameter to a + * DOM ProcessingInstruction, returning the DOM version. The DOM Node will + * be linked to an independent DOM Document instance supplied by the current + * DOMAdapter + * + * @param comment + * Comment to output. + * @return an org.w3c.dom.Comment version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Comment output(Comment comment) throws JDOMException { + return processor.process(adapter.createDocument(), format, comment); + } + + /** + * This converts the JDOM EntityRef parameter to a DOM + * EntityReference Node, returning the DOM version. The DOM Node will be + * linked to an independent DOM Document instance supplied by the current + * DOMAdapter + * + * @param entity + * EntityRef to output. + * @return an org.w3c.dom.EntityReference version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.EntityReference output(EntityRef entity) + throws JDOMException { + return processor.process(adapter.createDocument(), format, entity); + } + + /** + * This converts the JDOM Attribute parameter to a DOM Attr + * Node, returning the DOM version. The DOM Node will be linked to an + * independent DOM Document instance supplied by the current DOMAdapter + * + * @param attribute + * Attribute to output. + * @return an org.w3c.dom.Attr version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Attr output(Attribute attribute) throws JDOMException { + return processor.process(adapter.createDocument(), format, attribute); + } + + /** + * This converts the JDOM Attribute parameter to a DOM Attr + * Node, returning the DOM version. The DOM Node will be linked to an + * independent DOM Document instance supplied by the current DOMAdapter + * + * @param list + * Attribute to output. + * @return an org.w3c.dom.Attr version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public List output(List list) + throws JDOMException { + return processor.process(adapter.createDocument(), format, list); + } + + /** + * This converts the JDOM Element parameter to a DOM Element, + * returning the DOM version. The DOM Node will be linked to an independent + * DOM Document instance supplied by the current DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param element + * Element to output. + * @return an org.w3c.dom.Element version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Element output(org.w3c.dom.Document basedoc, + Element element) throws JDOMException { + return processor.process(basedoc, format, element); + } + + /** + * This converts the JDOM Text parameter to a DOM Text Node, + * returning the DOM version. The DOM Node will be linked to an independent + * DOM Document instance supplied by the current DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param text + * Text to output. + * @return an org.w3c.dom.Text version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Text output(org.w3c.dom.Document basedoc, Text text) + throws JDOMException { + return processor.process(basedoc, format, text); + } + + /** + * This converts the JDOM CDATA parameter to a DOM CDATASection + * Node, returning the DOM version. The DOM Node will be linked to an + * independent DOM Document instance supplied by the current DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param cdata + * CDATA to output. + * @return an org.w3c.dom.CDATASection version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.CDATASection output(org.w3c.dom.Document basedoc, + CDATA cdata) throws JDOMException { + return processor.process(basedoc, format, cdata); + } + + /** + * This converts the JDOM ProcessingInstruction parameter to a + * DOM ProcessingInstruction, returning the DOM version. The DOM Node will + * be linked to an independent DOM Document instance supplied by the current + * DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param pi + * ProcessingInstruction to output. + * @return an org.w3c.dom.Element version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.ProcessingInstruction output( + org.w3c.dom.Document basedoc, ProcessingInstruction pi) + throws JDOMException { + return processor.process(basedoc, format, pi); + } + + /** + * This converts the JDOM ProcessingInstruction parameter to a + * DOM ProcessingInstruction, returning the DOM version. The DOM Node will + * be linked to an independent DOM Document instance supplied by the current + * DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param comment + * Comment to output. + * @return an org.w3c.dom.Comment version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Comment output(org.w3c.dom.Document basedoc, + Comment comment) throws JDOMException { + return processor.process(basedoc, format, comment); + } + + /** + * This converts the JDOM EntityRef parameter to a DOM + * EntityReference Node, returning the DOM version. The DOM Node will be + * linked to an independent DOM Document instance supplied by the current + * DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param entity + * EntityRef to output. + * @return an org.w3c.dom.EntityReference version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.EntityReference output(org.w3c.dom.Document basedoc, + EntityRef entity) throws JDOMException { + return processor.process(basedoc, format, entity); + } + + /** + * This converts the JDOM Attribute parameter to a DOM Attr + * Node, returning the DOM version. The DOM Node will be linked to an + * independent DOM Document instance supplied by the current DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param attribute + * Attribute to output. + * @return an org.w3c.dom.Attr version + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public org.w3c.dom.Attr output(org.w3c.dom.Document basedoc, + Attribute attribute) throws JDOMException { + return processor.process(basedoc, format, attribute); + } + + /** + * This converts the list of JDOM Content in to a list of DOM + * Nodes, returning the DOM version. The DOM Node will be linked to an + * independent DOM Document instance supplied by the current DOMAdapter + * + * @param basedoc + * The DOM Document to use for creating DOM Nodes. + * @param list + * of JDOM Content to output. + * @return a List of org.w3c.dom.Node + * @throws JDOMException + * if output failed. + * @since JDOM2 + */ + public List output(org.w3c.dom.Document basedoc, + List list) throws JDOMException { + return processor.process(basedoc, format, list); + } + +} diff --git a/core/src/java/org/jdom/output/EscapeStrategy.java b/core/src/java/org/jdom/output/EscapeStrategy.java new file mode 100644 index 0000000..3767004 --- /dev/null +++ b/core/src/java/org/jdom/output/EscapeStrategy.java @@ -0,0 +1,75 @@ +/*-- + + Copyright (C) 2000-2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +/** + * Logic to determine which characters should be formatted as character + * entities. + * + * @author Alex Rosen + * @author Bradley S. Huffman + * @author Jason Hunter + */ +public interface EscapeStrategy { + + /** + * Test whether the supplied character should be formatted literally + * or as a character entity. + * @param ch The char to test to determine whether it should be escaped. + * @return true if ch should be escaped. + */ + public boolean shouldEscape(char ch); +} + diff --git a/core/src/java/org/jdom/output/Format.java b/core/src/java/org/jdom/output/Format.java new file mode 100644 index 0000000..4d4c651 --- /dev/null +++ b/core/src/java/org/jdom/output/Format.java @@ -0,0 +1,1043 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import java.nio.charset.Charset; +import java.nio.charset.CharsetEncoder; +import java.util.Locale; + +import org.jdom.IllegalDataException; +import org.jdom.Verifier; + +/** + * Class to encapsulate XMLOutputter format options. + * Typically users adapt the standard format configurations obtained by + * {@link #getRawFormat} (no whitespace changes), + * {@link #getPrettyFormat} (whitespace beautification), and + * {@link #getCompactFormat} (whitespace normalization). + *

+ * Several modes are available to effect the way textual content is printed. + * See the documentation for {@link TextMode} for details. + *

+ * Note about Line Separator: + *

+ * By default JDOM will always use the CRNL sequence "\r\n" for output. This + * can be changed in a number of different ways. See the {@link LineSeparator} + * enumeration for more information. + *

+ * Note about XML Character Escaping: + *

+ * JDOM will escape characters in the output based on the EscapeStrategy that + * is specified by this Format. The Format will by default use a sensible + * EscapeStrategy that is based on the character encoding of the output. If + * the default escape mechanism is not producing the correct results you can + * change the EscapeStrategy on the format to suit your own needs. + * + * + * @see LineSeparator + * + * @author Jason Hunter + * @author Rolf Lear + */ +public class Format implements Cloneable { + + /** + * An EscapeStrategy suitable for UTF-8 an UTF-16. We want the class to + * have its own name. + */ + private static final class EscapeStrategyUTF implements EscapeStrategy { + @Override + public final boolean shouldEscape(char ch) { + return Verifier.isHighSurrogate(ch); + } + } + + /** + * An EscapeStrategy suitable for UTF-8 an UTF-16 + */ + private static final EscapeStrategy UTFEscapeStrategy = new EscapeStrategyUTF(); + + /** + * An EscapeStrategy suitable for 8-bit charsets. We want the class to have + * its own name. + */ + private static final class EscapeStrategy8Bits implements EscapeStrategy { + @Override + public boolean shouldEscape(final char ch) { + return (ch >>> 8) != 0; + } + } + + /** + * An EscapeStrategy suitable for 8-bit charsets + */ + private static final EscapeStrategy Bits8EscapeStrategy = new EscapeStrategy8Bits(); + + /** + * An EscapeStrategy suitable for 7-bit charsets. We want the class to + * have its own name. + */ + private static final class EscapeStrategy7Bits implements EscapeStrategy { + @Override + public boolean shouldEscape(final char ch) { + return (ch >>> 7) != 0; + } + } + + /** + * An EscapeStrategy suitable for 7-bit charsets + */ + private static final EscapeStrategy Bits7EscapeStrategy = + new EscapeStrategy7Bits(); + + /** + * An EscapeStrategy suitable for 'unknown' charsets + */ + private static final EscapeStrategy DefaultEscapeStrategy = new EscapeStrategy() { + @Override + public boolean shouldEscape(char ch) { + if (Verifier.isHighSurrogate(ch)) { + return true; // Safer this way per http://unicode.org/faq/utf_bom.html#utf8-4 + } + + return false; + } + }; + + /** + * Handles Charsets. + */ + private final static class DefaultCharsetEscapeStrategy implements EscapeStrategy { + + private final CharsetEncoder encoder; + + public DefaultCharsetEscapeStrategy(CharsetEncoder cse) { + encoder = cse; + } + + @Override + public boolean shouldEscape(final char ch) { + + if (Verifier.isHighSurrogate(ch)) { + return true; // Safer this way per http://unicode.org/faq/utf_bom.html#utf8-4 + } + + return !encoder.canEncode(ch); + } + + } + + /** + * Returns a new Format object that performs no whitespace changes, uses + * the UTF-8 encoding, doesn't expand empty elements, includes the + * declaration and encoding, and uses the default entity escape strategy. + * Tweaks can be made to the returned Format instance without affecting + * other instances. + + * @return a Format with no whitespace changes + */ + public static Format getRawFormat() { + return new Format(); + } + + /** + * Returns a new Format object that performs whitespace beautification with + * 2-space indents, uses the UTF-8 encoding, doesn't expand empty elements, + * includes the declaration and encoding, and uses the default entity + * escape strategy. + * Tweaks can be made to the returned Format instance without affecting + * other instances. + * + * @return a Format with whitespace beautification + */ + public static Format getPrettyFormat() { + Format f = new Format(); + f.setIndent(STANDARD_INDENT); + f.setTextMode(TextMode.TRIM); + return f; + } + + /** + * Returns a new Format object that performs whitespace normalization, uses + * the UTF-8 encoding, doesn't expand empty elements, includes the + * declaration and encoding, and uses the default entity escape strategy. + * Tweaks can be made to the returned Format instance without affecting + * other instances. + * + * @return a Format with whitespace normalization + */ + public static Format getCompactFormat() { + Format f = new Format(); + f.setTextMode(TextMode.NORMALIZE); + return f; + } + + /** + * Use the XML Specification definition of whitespace to compact the + * input value. The value is trimmed, and any internal XML whitespace + * is replaced with a single ' ' space. + * @param str The value to compact. + * @return The compacted value + * @since JDOM2 + */ + public static final String compact(String str) { + int right = str.length() - 1; + int left = 0; + while (left <= right && + Verifier.isXMLWhitespace(str.charAt(left))) { + left++; + } + while (right > left && + Verifier.isXMLWhitespace(str.charAt(right))) { + right--; + } + + if (left > right) { + return ""; + } + + boolean space = true; + final StringBuilder buffer = new StringBuilder(right - left + 1); + while (left <= right) { + final char c = str.charAt(left); + if (Verifier.isXMLWhitespace(c)) { + if (space) { + buffer.append(' '); + space = false; + } + } else { + buffer.append(c); + space = true; + } + left++; + } + return buffer.toString(); + } + + /** + * Use the XML Specification definition of whitespace to Right-trim the + * input value. + * @param str The value to trim. + * @return The value right-trimmed + * @since JDOM2 + */ + public static final String trimRight(String str) { + int right = str.length() - 1; + while (right >= 0 && Verifier.isXMLWhitespace(str.charAt(right))) { + right--; + } + if (right < 0) { + return ""; + } + return str.substring(0, right + 1); + } + + /** + * Use the XML Specification definition of whitespace to Left-trim the + * input value. + * @param str The value to trim. + * @return The value left-trimmed + * @since JDOM2 + */ + public static final String trimLeft(final String str) { + final int right = str.length(); + int left = 0; + while (left < right && Verifier.isXMLWhitespace(str.charAt(left))) { + left++; + } + if (left >= right) { + return ""; + } + + return str.substring(left); + } + + /** + * Use the XML Specification definition of whitespace to trim the + * input value. + * @param str The value to trim. + * @return The value trimmed + * @since JDOM2 + */ + public static final String trimBoth(final String str) { + int right = str.length() - 1; + while (right > 0 && Verifier.isXMLWhitespace(str.charAt(right))) { + right--; + } + int left = 0; + while (left <= right && Verifier.isXMLWhitespace(str.charAt(left))) { + left++; + } + if (left > right) { + return ""; + } + return str.substring(left, right + 1); + } + + + /** + * This will take the three pre-defined entities in XML 1.0 ('<', '>', + * and '&' - used specifically in XML elements) as well as CR/NL, tabs, + * and Quote characters which require escaping inside Attribute values and + * converts their character representation to the appropriate entity + * reference suitable for XML attribute content. Further, some special + * characters (e.g. characters that are not valid in the current encoding) + * are converted to escaped representations. + *

+ * @param strategy + * The EscapeStrategy to query. + * @param value + * String Attribute value to escape. + * @return The value appropriately escaped. + * @throws IllegalDataException + * if an entity can not be escaped + */ + public static final String escapeAttribute(final EscapeStrategy strategy, + final String value) { + final int len = value.length(); + int idx = 0; + + checkloop: while (idx < len) { + final char ch = value.charAt(idx); + if (ch == '<' || ch == '>' || ch == '&' || ch == '\r' || ch == '\n' + || ch == '"' || ch == '\t' || strategy.shouldEscape(ch)) { + break checkloop; + } + idx++; + } + + if (idx == len) { + return value; + } + + char highsurrogate = 0; + final StringBuilder sb = new StringBuilder(len + 5); + sb.append(value, 0, idx); + while (idx < len) { + final char ch = value.charAt(idx++); + if (highsurrogate > 0) { + if (!Verifier.isLowSurrogate(ch)) { + throw new IllegalDataException( + "Could not decode surrogate pair 0x" + + Integer.toHexString(highsurrogate) + " / 0x" + + Integer.toHexString(ch)); + } + int chp = Verifier.decodeSurrogatePair(highsurrogate, ch); + sb.append("&#x"); + sb.append(Integer.toHexString(chp)); + sb.append(';'); + highsurrogate = 0; + continue; + } + switch (ch) { + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '&': + sb.append("&"); + break; + case '\r': + sb.append(" "); + break; + case '"': + sb.append("""); + break; + case '\t': + sb.append(" "); + break; + case '\n': + sb.append(" "); + break; + default: + + if (strategy.shouldEscape(ch)) { + // make sure what we are escaping is not the + // beginning of a multi-byte character. + if (Verifier.isHighSurrogate(ch)) { + // this is a the high of a surrogate pair + highsurrogate = ch; + } else { + sb.append("&#x"); + sb.append(Integer.toHexString(ch)); + sb.append(';'); + } + } else { + sb.append(ch); + } + break; + } + } + if (highsurrogate > 0) { + throw new IllegalDataException("Surrogate pair 0x" + + Integer.toHexString(highsurrogate) + "truncated"); + } + + return sb.toString(); + } + + + + + /** + * This will take the three pre-defined entities in XML 1.0 ('<', '>', + * and '&' - used specifically in XML elements) and convert their + * character representation to the appropriate entity reference, suitable + * for XML element content. Further, some special characters (e.g. + * characters that are not valid in the current encoding) are converted to + * escaped representations. If the eol parameter is not null, then any + * internal newlines will be replaced with the specified eol sequence. + * + * @param strategy + * The EscapeStrategy + * @param eol + * The End-Of-Line sequence to be used (may be null). + * @param value + * The String to escape + * @return The input value escaped. + * @throws IllegalDataException + * if an entity can not be escaped + * @since JDOM2 + */ + public static final String escapeText(final EscapeStrategy strategy, + final String eol, final String value) { + final int right = value.length(); + int idx = 0; + checkloop: while (idx < right) { + final char ch = value.charAt(idx); + if (ch == '<' || ch == '>' || ch == '&' || ch == '\r' || ch == '\n' + || strategy.shouldEscape(ch)) { + break checkloop; + } + idx++; + } + + if (idx == right) { + // no escape needed. + return value; + } + + StringBuilder sb = new StringBuilder(); + if (idx > 0) { + sb.append(value, 0, idx); + } + char highsurrogate = 0; + while (idx < right) { + final char ch = value.charAt(idx++); + if (highsurrogate > 0) { + if (!Verifier.isLowSurrogate(ch)) { + throw new IllegalDataException( + "Could not decode surrogate pair 0x" + + Integer.toHexString(highsurrogate) + " / 0x" + + Integer.toHexString(ch)); + } + int chp = Verifier.decodeSurrogatePair(highsurrogate, ch); + sb.append("&#x" + Integer.toHexString(chp) + ";"); + highsurrogate = 0; + continue; + } + switch (ch) { + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '&': + sb.append("&"); + break; + case '\r': + sb.append(" "); + break; + case '\n': + if (eol != null) { + sb.append(eol); + } else { + sb.append('\n'); + } + break; + default: + + if (strategy.shouldEscape(ch)) { + // make sure what we are escaping is not the + // beginning of a multi-byte character. + if (Verifier.isHighSurrogate(ch)) { + // this is a the high of a surrogate pair + highsurrogate = ch; + } else { + sb.append("&#x" + Integer.toHexString(ch) + ";"); + } + } else { + sb.append(ch); + } + break; + } + } + if (highsurrogate > 0) { + throw new IllegalDataException("Surrogate pair 0x" + + Integer.toHexString(highsurrogate) + "truncated"); + } + + return sb.toString(); + + } + + private static final EscapeStrategy chooseStrategy(String encoding) { + if ("UTF-8".equalsIgnoreCase(encoding) || + "UTF-16".equalsIgnoreCase(encoding)) { + return UTFEscapeStrategy; + } + + // Note issue #149: https://github.com/hunterhacker/jdom/issues/149 + // require locale for case conversion to avoid potential security issue. + if (encoding.toUpperCase(Locale.ENGLISH).startsWith("ISO-8859-") || + "Latin1".equalsIgnoreCase(encoding)) { + return Bits8EscapeStrategy; + } + + if ("US-ASCII".equalsIgnoreCase(encoding) || + "ASCII".equalsIgnoreCase(encoding)) { + return Bits7EscapeStrategy; + } + + try { + final CharsetEncoder cse = Charset.forName(encoding).newEncoder(); + return new DefaultCharsetEscapeStrategy(cse); + } catch (Exception e) { + // swallow that... and assume false. + } + return DefaultEscapeStrategy; + } + + + /** standard value to indent by, if we are indenting */ + private static final String STANDARD_INDENT = " "; + + /** standard string with which to end a line */ + private static final String STANDARD_LINE_SEPARATOR = LineSeparator.DEFAULT.value(); + + /** standard encoding */ + private static final String STANDARD_ENCODING = "UTF-8"; + + + /** The default indent is no spaces (as original document) */ + String indent = null; + + /** New line separator */ + String lineSeparator = STANDARD_LINE_SEPARATOR; + + /** The encoding format */ + String encoding = STANDARD_ENCODING; + + /** Whether or not to output the XML declaration + * - default is false */ + boolean omitDeclaration = false; + + /** Whether or not to output the encoding in the XML declaration + * - default is false */ + boolean omitEncoding = false; + + /** Whether Attributes that are defaulted from the DTD or Schema + * are output. */ + boolean specifiedAttributesOnly = false; + + /** Whether or not to expand empty elements to + * <tagName></tagName> - default is false */ + boolean expandEmptyElements = false; + + /** Whether TrAX output escaping disabling/enabling PIs are ignored + * or processed - default is false */ + boolean ignoreTrAXEscapingPIs = false; + + /** text handling mode */ + TextMode mode = TextMode.PRESERVE; + + /** entity escape logic */ + EscapeStrategy escapeStrategy = DefaultEscapeStrategy; + + /** + * Creates a new Format instance with default (raw) behavior. + */ + private Format() { + setEncoding(STANDARD_ENCODING); + } + + /** + * Sets the {@link EscapeStrategy} to use for character escaping. + * + * @param strategy the EscapeStrategy to use + * @return a pointer to this Format for chaining + */ + public Format setEscapeStrategy(EscapeStrategy strategy) { + escapeStrategy = strategy; + return this; + } + + /** + * Returns the current escape strategy + * + * @return the current escape strategy + */ + public EscapeStrategy getEscapeStrategy() { + return escapeStrategy; + } + + /** + * This will set the newline separator (LineSeparator). + * The default is \r\n. + *

+ * Use the {@link #setLineSeparator(LineSeparator)} method to set + * standard separators in an easier way. + *

+ * To make it output the system default line ending string, call + * setLineSeparator(System.getProperty("line.separator")). + * + *

+ * To output "UNIX-style" documents, call + * setLineSeparator("\n"). To output "Mac-style" + * documents, call setLineSeparator("\r"). DOS-style + * documents use CR-LF ("\r\n"), which is the default. + *

+ * + *

+ * Note that this only applies to newlines generated by the + * outputter. All XML parsers are required to 'normalize' all the + * combinations of line seperators to just '\n'. As a result, if any JDOM + * component has an end-of-line-like value (e.g. '\r') in it then that value + * must be the result of an escaped value in the XML source document + * &#xD; or a value explicitly set with one of the Text + * value setters. Values in JDOM content that were explicitly set to be + * '\r' will always be escaped on XML Output. + *

+ * The actual newline separator itself though can be set with this method. + * Any internal newlines in Text output will be represented by this + * end-of-line sequence. For example, the following code: + *

+ *

+	 *   Text txt = new Text("\r\n");
+	 *   XMLOutputter xout = new XMLOutputter();
+	 *   String result = xout.outputString(txt);
+	 * 
+ * will produce the literal String sequence "&#xD;\r\n" because the + * original \r is escaped to be &#xD; and the original \n + * is replaced with the JDOM default Line Separator "\r\n". + * + *

+ * If the format's "indent" property is null (as is the default + * for the Raw and Compact formats), then this value only effects the + * newlines written after the declaration and doctype, as well as any + * newlines embedded within existing text content. + *

+ * Setting the indent to be null will disable end-of-line processing + * for any formatting, but will not affect substitution of embedded \n. + * Setting this value to null or the empty string will disable all + * end-of-line modifications. + * + * @see #setTextMode + * @see #setLineSeparator(LineSeparator) + * + * @param separator String line separator to use. + * @return a pointer to this Format for chaining + */ + public Format setLineSeparator(String separator) { + this.lineSeparator = "".equals(separator) ? null : separator; + return this; + } + + /** + * This will set the newline separator sequence. + *

+ * This method differes from {@link #setLineSeparator(String)} slightly in + * that, to disable end-of-line processing you should call: + *

+	 * Format.setLinewSeparator(LineSeparator.NONE);
+	 * 
+ * + * @see #setLineSeparator(String) for comprehensive notes. + * + * @param separator {@link LineSeparator} line separator to us + * @return a pointer to this Format for chaining + * @since JDOM2 + */ + public Format setLineSeparator(LineSeparator separator) { + return setLineSeparator(separator == null ? + STANDARD_LINE_SEPARATOR : + separator.value()); + } + + /** + * Returns the current line separator. + * + * @return the current line separator + */ + public String getLineSeparator() { + return lineSeparator; + } + + /** + * This will set whether the XML declaration + * (<?xml version="1.0" + * encoding="UTF-8"?>) + * includes the encoding of the document. It is common to omit + * this in uses such as WML and other wireless device protocols. + * + * @param omitEncoding boolean indicating whether or not + * the XML declaration should indicate the document encoding. + * @return a pointer to this Format for chaining + */ + public Format setOmitEncoding(boolean omitEncoding) { + this.omitEncoding = omitEncoding; + return this; + } + + /** + * Returns whether the XML declaration encoding will be omitted. + * + * @return whether the XML declaration encoding will be omitted + */ + public boolean getOmitEncoding() { + return omitEncoding; + } + + /** + * This will set whether the XML declaration + * (<?xml version="1.0"?>) + * will be omitted or not. It is common to omit this in uses such + * as SOAP and XML-RPC calls. + * + * @param omitDeclaration boolean indicating whether or not + * the XML declaration should be omitted. + * @return a pointer to this Format for chaining + */ + public Format setOmitDeclaration(boolean omitDeclaration) { + this.omitDeclaration = omitDeclaration; + return this; + } + + /** + * Returns whether the XML declaration will be omitted. + * + * @return whether the XML declaration will be omitted + */ + public boolean getOmitDeclaration() { + return omitDeclaration; + } + + /** + * This will set whether empty elements are expanded from + * <tagName/> to + * <tagName></tagName>. + * + * @param expandEmptyElements boolean indicating whether or not + * empty elements should be expanded. + * @return a pointer to this Format for chaining + */ + public Format setExpandEmptyElements(boolean expandEmptyElements) { + this.expandEmptyElements = expandEmptyElements; + return this; + } + + /** + * Returns whether empty elements are expanded. + * + * @return whether empty elements are expanded + */ + public boolean getExpandEmptyElements() { + return expandEmptyElements; + } + + /** + * This will set whether JAXP TrAX processing instructions for + * disabling/enabling output escaping are ignored. Disabling + * output escaping allows using XML text as element content and + * outputing it verbatim, i.e. as element children would be. + *

+ * When processed, these processing instructions are removed from + * the generated XML text and control whether the element text + * content is output verbatim or with escaping of the pre-defined + * entities in XML 1.0. The text to be output verbatim shall be + * surrounded by the + * <?javax.xml.transform.disable-output-escaping ?> + * and <?javax.xml.transform.enable-output-escaping ?> + * PIs.

+ *

+ * When ignored, the processing instructions are present in the + * generated XML text and the pre-defined entities in XML 1.0 are + * escaped. + *

+ * Default: false.

+ * + * @param ignoreTrAXEscapingPIs boolean indicating + * whether or not TrAX ouput escaping PIs are ignored. + * + * @see javax.xml.transform.Result#PI_ENABLE_OUTPUT_ESCAPING + * @see javax.xml.transform.Result#PI_DISABLE_OUTPUT_ESCAPING + */ + public void setIgnoreTrAXEscapingPIs(boolean ignoreTrAXEscapingPIs) { + this.ignoreTrAXEscapingPIs = ignoreTrAXEscapingPIs; + } + + /** + * Returns whether JAXP TrAX processing instructions for + * disabling/enabling output escaping are ignored. + * + * @return whether or not TrAX ouput escaping PIs are ignored. + */ + public boolean getIgnoreTrAXEscapingPIs() { + return ignoreTrAXEscapingPIs; + } + + /** + * This sets the text output style. Options are available as static + * {@link TextMode} instances. The default is {@link TextMode#PRESERVE}. + * + * @param mode The TextMode to set. + * @return a pointer to this Format for chaining + */ + public Format setTextMode(Format.TextMode mode) { + this.mode = mode; + return this; + } + + /** + * Returns the current text output style. + * + * @return the current text output style + */ + public Format.TextMode getTextMode() { + return mode; + } + + /** + * This will set the indent String to use; this + * is usually a String of empty spaces. If you pass + * the empty string (""), then no indentation will happen but newlines + * will still be generated. Passing null will result in no indentation + * and no newlines generated. Default: none (null) + * + * @param indent String to use for indentation. + * @return a pointer to this Format for chaining + */ + public Format setIndent(String indent) { + this.indent = indent; + return this; + } + + /** + * Returns the indent string in use. + * + * @return the indent string in use + */ + public String getIndent() { + return indent; + } + + /** + * Sets the output encoding. The name should be an accepted XML + * encoding. + * + * @param encoding the encoding format. Use XML-style names like + * "UTF-8" or "ISO-8859-1" or "US-ASCII" + * @return a pointer to this Format for chaining + */ + public Format setEncoding(String encoding) { + this.encoding = encoding; + escapeStrategy = chooseStrategy(encoding); + return this; + } + + /** + * Returns the configured output encoding. + * + * @return the output encoding + */ + public String getEncoding() { + return encoding; + } + + + /** + * Will Attributes defaulted from the DTD or XMLSchema + * be output + * @return true if the defaulted Attributes will be output + */ + public boolean isSpecifiedAttributesOnly() { + return specifiedAttributesOnly; + } + + /** + * Set whether only those Attributes specified in the input XML should + * be output. Other Attributes (those defaulted or 'fixed' in the DTD + * or XMLSchema) should be ignored. + * @param specifiedAttributesOnly true if the defaulted + * Attributes should be ignored, false if they should be output + */ + public void setSpecifiedAttributesOnly(boolean specifiedAttributesOnly) { + this.specifiedAttributesOnly = specifiedAttributesOnly; + } + + @Override + public Format clone() { + Format format = null; + + try { + format = (Format) super.clone(); + } + catch (CloneNotSupportedException ce) { + // swallow. + } + + return format; + } + + /** + * Class to signify how text should be handled on output. The following + * table provides details. + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
+ * Text Mode + * + * Resulting behavior. + *
+ * PRESERVE (Default) + * + * All content is printed in the format it was created, no whitespace + * or line separators are are added or removed. + *
+ * TRIM_FULL_WHITE + * + * Content between tags consisting of all whitespace is not printed. + * If the content contains even one non-whitespace character, it is + * all printed verbatim, whitespace and all. + *
+ * TRIM + * + * All leading and trailing whitespace is trimmed. + *
+ * NORMALIZE + * + * Leading and trailing whitespace is trimmed, and any 'internal' + * whitespace is compressed to a single space. + *
+ * + * In most cases textual content is aligned with the surrounding tags + * (after the appropriate text mode is applied). In the case where the only + * content between the start and end tags is textual, the start tag, text, + * and end tag are all printed on the same line. If the document being + * output already has whitespace, it's wise to turn on TRIM mode so the + * pre-existing whitespace can be trimmed before adding new whitespace. + *

+ * When an element has a xml:space attribute with the value of "preserve", + * all formating is turned off (actually, the TextMode is set to + * {@link #PRESERVE} until the element and its contents have been printed. + * If a nested element contains another xml:space with the value "default" + * formatting is turned back on for the child element and then off for the + * remainder of the parent element. + * + * @since JDOM2 + */ + public static enum TextMode { + /** + * Mode for literal text preservation. + */ + PRESERVE, + + /** + * Mode for text trimming (left and right trim). + */ + TRIM, + + /** + * Mode for text normalization (left and right trim plus internal + * whitespace is normalized to a single space. + * @see org.jdom.Element#getTextNormalize + */ + NORMALIZE, + + /** + * Mode for text trimming of content consisting of nothing but + * whitespace but otherwise not changing output. + */ + TRIM_FULL_WHITE; + + } +} diff --git a/core/src/java/org/jdom/output/JDOMLocator.java b/core/src/java/org/jdom/output/JDOMLocator.java new file mode 100644 index 0000000..f559450 --- /dev/null +++ b/core/src/java/org/jdom/output/JDOMLocator.java @@ -0,0 +1,85 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import org.xml.sax.Locator; + +/** + * An implementation of the SAX {@link Locator} interface that + * exposes the JDOM node being processed by SAXOutputter. + *

+ * In JDOM2 this class is demoted to an interface. The information was never + * accurate anyway, and as an interface a specific Outputter instance can + * instead do 'the right thing' with the locator, if needed. + *

+ * This change breaks a possible compatibility with anyone who happened to treat + * the JDOMLocator to be 'settable'. This used to extend LocatorImpl class which + * had setter methods for the ColumnNumber, Line, PublicID, SystemID + * + * @author Laurent Bihanic + * @author Rolf Lear + * + */ +public interface JDOMLocator extends Locator { + + /** + * Returns the JDOM node being processed by SAXOutputter. + * + * @return the JDOM node being processed by SAXOutputter. + */ + public Object getNode(); + +} + diff --git a/core/src/java/org/jdom/output/LineSeparator.java b/core/src/java/org/jdom/output/LineSeparator.java new file mode 100644 index 0000000..cbf7b76 --- /dev/null +++ b/core/src/java/org/jdom/output/LineSeparator.java @@ -0,0 +1,206 @@ +/*-- + + Copyright (C) 2011 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import org.jdom.JDOMConstants; +import org.jdom.internal.SystemProperty; + +/** + * An enumeration of common separators that are used for JDOM output. + *

+ * These enumerated values can be used as input to the + * {@link Format#setLineSeparator(LineSeparator)} method. Additionally, the + * names of these constants can be also be used in the System Property + * {@link JDOMConstants#JDOM2_PROPERTY_LINE_SEPARATOR} which is used to + * define the default Line Separator sequence for JDOM output. See + * {@link #DEFAULT} Javadoc. + * + *

+ * JDOM has historically used the CR/NL sequence '\r\n' as a line-terminator. + * This sequence has the advantage that the output is easily opened in the + * 'Notepad' editor on Windows. Other editors on other platforms are typically + * smart enough to automatically adjust to whatever termination sequence is + * used in the document. The XML specification requires that the CR/NL sequence + * should be 'normalized' to a single newline '\n' when the document is parsed + * (XML 1.1 End-Of-Line + * Handling). As a result there is no XML issue with the JDOM default CR/NL + * end-of-line sequence. + *

+ * It should be noted that because JDOM internally stores just a '\n' as a line + * separator that any other output separator requires additional processing to + * output. There is a distinct performance benefit for using the UNIX, or NL + * LineSeparator for output. + *

+ * JDOM has always allowed the line-terminating sequence to be customised (or + * even disabled) for each {@link XMLOutputter2} operation by using this Format + * class. + *

+ * JDOM2 introduces two new features in relation to the end-of-line sequence. + * Firstly, it introduces this new {@link LineSeparator} enumeration which + * formalises the common line separators that can be used. In addition to the + * simple String-based {@link Format#setLineSeparator(String)} method you can + * now also call {@link Format#setLineSeparator(LineSeparator)} with one of the + * common enumerations. + *

+ * The second new JDOM2 feature is the ability to set a global default + * end-of-line sequence. JDOM 1.x forced the default sequence to be the CRLF + * sequence, but JDOM2 allows you to set the system property + * {@link JDOMConstants#JDOM2_PROPERTY_LINE_SEPARATOR} which will be used as the + * default sequence for Format. You can set the property to be the name of one + * of these LineSeparator enumerations too. For example, the following will + * cause all default Format instances to use the System-dependent end-of-line + * sequence instead of always CRLF: + *

+ *

+ * java -Dorg.jdom.output.LineSeparator=SYSTEM ...
+ * 
+ * + * @since JDOM2 + * @author Rolf Lear + * + */ +public enum LineSeparator { + /** + * The Separator sequence CRNL which is '\r\n'. + * This is the default sequence. + */ + CRNL("\r\n"), + + /** + * The Separator sequence NL which is '\n'. + */ + NL("\n"), + /** + * The Separator sequence CR which is '\r'. + */ + CR("\r"), + + /** The 'DOS' Separator sequence CRLF (CRNL) which is '\r\n'. */ + DOS("\r\n"), + + /** The 'UNIX' Separator sequence NL which is '\n'. */ + UNIX("\n"), + + + /** + * The system-dependent Separator sequence NL which is obtained from + * System.getProperty("line.separator"). This should be + * the equivalent of {@link #DOS} on windows platforms, and + * of {@link #UNIX} on UNIX and Apple systems (after Mac OSX). + */ + SYSTEM(SystemProperty.get("line.separator", "\r\n")), + + /** Perform no end-of-line processing. */ + NONE(null), + + /** + * Use the sequence '\r\n' unless the System property + * {@link JDOMConstants#JDOM2_PROPERTY_LINE_SEPARATOR} is defined, in which + * case use the value specified in that property. If the value in that + * property matches one of the Enumeration names (e.g. SYSTEM) then use the + * sequence specified in that enumeration. + */ + // DEFAULT must be declared last so that you can specify enum names + // in the system property. + DEFAULT(getDefaultLineSeparator()); + + + + private static String getDefaultLineSeparator() { + // Android has some unique ordering requirements in this bootstrap process. + // also, Android will not have the system property set, so we can exit with the null. + final String prop = SystemProperty.get(JDOMConstants.JDOM2_PROPERTY_LINE_SEPARATOR, "DEFAULT"); + if ("DEFAULT".equals(prop)) { + // need to do this to catch the normal process where the property is not set + // which will cause the value 'DEFAULT' to be returned by the getProperty(), + // or in an unlikely instance when someone sets + // -Dorg.jdom.output.LineSeparator=DEFAULT + // which would create some sort of loop to happen.... + return "\r\n"; + } else if ("SYSTEM".equals(prop)) { + return System.getProperty("line.separator"); + } else if ("CRNL".equals(prop)) { + return "\r\n"; + } else if ("NL".equals(prop)) { + return "\n"; + } else if ("CR".equals(prop)) { + return "\r"; + } else if ("DOS".equals(prop)) { + return "\r\n"; + } else if ("UNIX".equals(prop)) { + return "\n"; + } else if ("NONE".equals(prop)) { + return null; + } + return prop; + } + + + + private final String value; + + LineSeparator(String value) { + this.value = value; + } + + /** + * The String sequence used for this Separator + * @return an End-Of-Line String + */ + public String value() { + return value; + } + +} diff --git a/core/src/java/org/jdom/output/NamespaceStack.java b/core/src/java/org/jdom/output/NamespaceStack.java new file mode 100644 index 0000000..378429c --- /dev/null +++ b/core/src/java/org/jdom/output/NamespaceStack.java @@ -0,0 +1,154 @@ +/*-- + + $Id: NamespaceStack.java,v 1.14 2007/11/10 05:29:01 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ +package org.jdom.output; + +import java.util.*; + +import org.jdom.Namespace; + +/** + * A non-public utility class used by both {@link XMLOutputter} and + * {@link SAXOutputter} to manage namespaces in a JDOM Document + * during output. + * + * @version $Revision: 1.14 $, $Date: 2007/11/10 05:29:01 $ + * @author Elliotte Rusty Harolde + * @author Fred Trimble + * @author Brett McLaughlin + */ +class NamespaceStack { + + private static final String CVS_ID = + "@(#) $RCSfile: NamespaceStack.java,v $ $Revision: 1.14 $ $Date: 2007/11/10 05:29:01 $ $Name: $"; + + /** The prefixes available */ + private Stack prefixes; + + /** The URIs available */ + private Stack uris; + + /** + * This creates the needed storage. + */ + NamespaceStack() { + prefixes = new Stack(); + uris = new Stack(); + } + + /** + * This will add a new {@link Namespace} + * to those currently available. + * + * @param ns Namespace to add. + */ + public void push(Namespace ns) { + prefixes.push(ns.getPrefix()); + uris.push(ns.getURI()); + } + + /** + * This will remove the topmost (most recently added) + * {@link Namespace}, and return its prefix. + * + * @return String - the popped namespace prefix. + */ + public String pop() { + String prefix = (String)prefixes.pop(); + uris.pop(); + + return prefix; + } + + /** + * This returns the number of available namespaces. + * + * @return int - size of the namespace stack. + */ + public int size() { + return prefixes.size(); + } + + /** + * Given a prefix, this will return the namespace URI most + * rencently (topmost) associated with that prefix. + * + * @param prefix String namespace prefix. + * @return String - the namespace URI for that prefix. + */ + public String getURI(String prefix) { + int index = prefixes.lastIndexOf(prefix); + if (index == -1) { + return null; + } + String uri = (String)uris.elementAt(index); + return uri; + } + + /** + * This will print out the size and current stack, from the + * most recently added {@link Namespace} to + * the "oldest," all to System.out. + */ + public String toString() { + StringBuffer buf = new StringBuffer(); + String sep = System.getProperty("line.separator"); + buf.append("Stack: " + prefixes.size() + sep); + for (int i = 0; i < prefixes.size(); i++) { + buf.append(prefixes.elementAt(i) + "&" + uris.elementAt(i) + sep); + } + return buf.toString(); + } +} diff --git a/core/src/java/org/jdom/output/SAXOutputter.java b/core/src/java/org/jdom/output/SAXOutputter.java new file mode 100644 index 0000000..fd39bc5 --- /dev/null +++ b/core/src/java/org/jdom/output/SAXOutputter.java @@ -0,0 +1,813 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import static org.jdom.JDOMConstants.*; + +import java.util.List; + +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; + +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.support.AbstractSAXOutputProcessor; +import org.jdom.output.support.SAXOutputProcessor; +import org.jdom.output.support.SAXTarget; + +/** + * Outputs a JDOM document as a stream of SAX2 events. + *

+ * Most ContentHandler callbacks are supported. BOTH + * ignorableWhitespace() and skippedEntity() have not + * been implemented. The {@link JDOMLocator} class returned by + * {@link #getLocator} exposes the current node being operated + * upon. + *

+ * At this time, it is not possible to access notations and unparsed entity + * references in a DTD from JDOM. Therefore, DTDHandler callbacks + * have not been implemented yet. + *

+ * The ErrorHandler callbacks have not been implemented, since + * these are supposed to be invoked when the document is parsed and at this + * point the document exists in memory and is known to have no errors. + *

+ * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Fred Trimble + * @author Bradley S. Huffman + */ +public class SAXOutputter { + + private static final class DefaultSAXOutputProcessor extends + AbstractSAXOutputProcessor { + // nothing. + } + + private static final SAXOutputProcessor DEFAULT_PROCESSOR = new DefaultSAXOutputProcessor(); + + /** registered ContentHandler */ + private ContentHandler contentHandler; + + /** registered ErrorHandler */ + private ErrorHandler errorHandler; + + /** registered DTDHandler */ + private DTDHandler dtdHandler; + + /** registered EntityResolver */ + private EntityResolver entityResolver; + + /** registered LexicalHandler */ + private LexicalHandler lexicalHandler; + + /** registered DeclHandler */ + private DeclHandler declHandler; + + /** + * Whether to report attribute namespace declarations as xmlns attributes. + * Defaults to false as per SAX specifications. + * + * @see SAX + * namespace specifications + */ + private boolean declareNamespaces = false; + + /** + * Whether to report DTD events to DeclHandlers and LexicalHandlers. + * Defaults to true. + */ + private boolean reportDtdEvents = true; + + /** + * A SAXOutputProcessor + */ + private SAXOutputProcessor processor = DEFAULT_PROCESSOR; + + /** + * The Format to use for output. + */ + private Format format = Format.getRawFormat(); + + /** + * This will create a SAXOutputter without any registered + * handler. The application is then responsible for registering them using + * the setXxxHandler() methods. + */ + public SAXOutputter() { + } + + /** + * This will create a SAXOutputter with the specified + * ContentHandler. + * + * @param contentHandler + * contains ContentHandler callback methods + */ + public SAXOutputter(ContentHandler contentHandler) { + this(contentHandler, null, null, null, null); + } + + /** + * This will create a SAXOutputter with the specified SAX2 + * handlers. At this time, only ContentHandler and + * EntityResolver are supported. + * + * @param contentHandler + * contains ContentHandler callback methods + * @param errorHandler + * contains ErrorHandler callback methods + * @param dtdHandler + * contains DTDHandler callback methods + * @param entityResolver + * contains EntityResolver callback methods + */ + public SAXOutputter(ContentHandler contentHandler, + ErrorHandler errorHandler, DTDHandler dtdHandler, + EntityResolver entityResolver) { + this(contentHandler, errorHandler, dtdHandler, entityResolver, null); + } + + /** + * This will create a SAXOutputter with the specified SAX2 + * handlers. At this time, only ContentHandler and + * EntityResolver are supported. + * + * @param contentHandler + * contains ContentHandler callback methods + * @param errorHandler + * contains ErrorHandler callback methods + * @param dtdHandler + * contains DTDHandler callback methods + * @param entityResolver + * contains EntityResolver callback methods + * @param lexicalHandler + * contains LexicalHandler callbacks. + */ + public SAXOutputter(ContentHandler contentHandler, + ErrorHandler errorHandler, DTDHandler dtdHandler, + EntityResolver entityResolver, LexicalHandler lexicalHandler) { + this.contentHandler = contentHandler; + this.errorHandler = errorHandler; + this.dtdHandler = dtdHandler; + this.entityResolver = entityResolver; + this.lexicalHandler = lexicalHandler; + } + + /** + * This will create a SAXOutputter with the specified SAX2 + * handlers. At this time, only ContentHandler and + * EntityResolver are supported. + * + * @param processor + * the {@link SAXOutputProcessor} to use for output. + * @param format + * the {@link Format} to use for output. + * @param contentHandler + * contains ContentHandler callback methods + * @param errorHandler + * contains ErrorHandler callback methods + * @param dtdHandler + * contains DTDHandler callback methods + * @param entityResolver + * contains EntityResolver callback methods + * @param lexicalHandler + * contains LexicalHandler callbacks. + */ + public SAXOutputter(SAXOutputProcessor processor, Format format, + ContentHandler contentHandler, ErrorHandler errorHandler, + DTDHandler dtdHandler, EntityResolver entityResolver, + LexicalHandler lexicalHandler) { + this.processor = processor == null ? DEFAULT_PROCESSOR : processor; + this.format = format == null ? Format.getRawFormat() : format; + this.contentHandler = contentHandler; + this.errorHandler = errorHandler; + this.dtdHandler = dtdHandler; + this.entityResolver = entityResolver; + this.lexicalHandler = lexicalHandler; + } + + /** + * This will set the ContentHandler. + * + * @param contentHandler + * contains ContentHandler callback methods. + */ + public void setContentHandler(ContentHandler contentHandler) { + this.contentHandler = contentHandler; + } + + /** + * Returns the registered ContentHandler. + * + * @return the current ContentHandler or null if + * none was registered. + */ + public ContentHandler getContentHandler() { + return this.contentHandler; + } + + /** + * This will set the ErrorHandler. + * + * @param errorHandler + * contains ErrorHandler callback methods. + */ + public void setErrorHandler(ErrorHandler errorHandler) { + this.errorHandler = errorHandler; + } + + /** + * Return the registered ErrorHandler. + * + * @return the current ErrorHandler or null if + * none was registered. + */ + public ErrorHandler getErrorHandler() { + return this.errorHandler; + } + + /** + * This will set the DTDHandler. + * + * @param dtdHandler + * contains DTDHandler callback methods. + */ + public void setDTDHandler(DTDHandler dtdHandler) { + this.dtdHandler = dtdHandler; + } + + /** + * Return the registered DTDHandler. + * + * @return the current DTDHandler or null if none + * was registered. + */ + public DTDHandler getDTDHandler() { + return this.dtdHandler; + } + + /** + * This will set the EntityResolver. + * + * @param entityResolver + * contains EntityResolver callback methods. + */ + public void setEntityResolver(EntityResolver entityResolver) { + this.entityResolver = entityResolver; + } + + /** + * Return the registered EntityResolver. + * + * @return the current EntityResolver or null if + * none was registered. + */ + public EntityResolver getEntityResolver() { + return this.entityResolver; + } + + /** + * This will set the LexicalHandler. + * + * @param lexicalHandler + * contains lexical callback methods. + */ + public void setLexicalHandler(LexicalHandler lexicalHandler) { + this.lexicalHandler = lexicalHandler; + } + + /** + * Return the registered LexicalHandler. + * + * @return the current LexicalHandler or null if + * none was registered. + */ + public LexicalHandler getLexicalHandler() { + return this.lexicalHandler; + } + + /** + * This will set the DeclHandler. + * + * @param declHandler + * contains declaration callback methods. + */ + public void setDeclHandler(DeclHandler declHandler) { + this.declHandler = declHandler; + } + + /** + * Return the registered DeclHandler. + * + * @return the current DeclHandler or null if none + * was registered. + */ + public DeclHandler getDeclHandler() { + return this.declHandler; + } + + /** + * Returns whether attribute namespace declarations shall be reported as + * "xmlns" attributes. + * + * @return whether attribute namespace declarations shall be reported as + * "xmlns" attributes. + */ + public boolean getReportNamespaceDeclarations() { + return declareNamespaces; + } + + /** + * This will define whether attribute namespace declarations shall be + * reported as "xmlns" attributes. This flag defaults to false + * and behaves as the "namespace-prefixes" SAX core feature. + * + * @param declareNamespaces + * whether attribute namespace declarations shall be reported as + * "xmlns" attributes. + */ + public void setReportNamespaceDeclarations(boolean declareNamespaces) { + this.declareNamespaces = declareNamespaces; + } + + /** + * Returns whether DTD events will be reported. + * + * @return whether DTD events will be reported + */ + public boolean getReportDTDEvents() { + return reportDtdEvents; + } + + /** + * This will define whether to report DTD events to SAX DeclHandlers and + * LexicalHandlers if these handlers are registered and the document to + * output includes a DocType declaration. + * + * @param reportDtdEvents + * whether to notify DTD events. + */ + public void setReportDTDEvents(boolean reportDtdEvents) { + this.reportDtdEvents = reportDtdEvents; + } + + /** + * This will set the state of a SAX feature. + *

+ * All XMLReaders are required to support setting to true and to false. + *

+ *

+ * SAXOutputter currently supports the following SAX core features: + *

+ *
http://xml.org/sax/features/namespaces
+ *
description: true indicates namespace + * URIs and unprefixed local names for element and attribute names will be + * available
+ *
access: read/write, but always true!
+ *
http://xml.org/sax/features/namespace-prefixes
+ *
description: true indicates XML 1.0 + * names (with prefixes) and attributes (including xmlns* attributes) will + * be available
+ *
access: read/write
+ *
http://xml.org/sax/features/validation
+ *
description: controls whether SAXOutputter is + * reporting DTD-related events; if true, the DocType internal + * subset will be parsed to fire DTD events
+ *
access: read/write, defaults to true
+ *
+ *

+ * + * @param name + * String the feature name, which is a fully-qualified + * URI. + * @param value + * boolean the requested state of the feature (true or + * false). + * @throws SAXNotRecognizedException + * when SAXOutputter does not recognize the feature name. + * @throws SAXNotSupportedException + * when SAXOutputter recognizes the feature name but cannot set the + * requested value. + */ + public void setFeature(String name, boolean value) + throws SAXNotRecognizedException, SAXNotSupportedException { + if (SAX_FEATURE_NAMESPACE_PREFIXES.equals(name)) { + // Namespace prefix declarations. + this.setReportNamespaceDeclarations(value); + } else { + if (SAX_FEATURE_NAMESPACES.equals(name)) { + if (value != true) { + // Namespaces feature always supported by SAXOutputter. + throw new SAXNotSupportedException(name); + } + // Else: true is OK! + } else { + if (SAX_FEATURE_VALIDATION.equals(name)) { + // Report DTD events. + this.setReportDTDEvents(value); + } else { + // Not a supported feature. + throw new SAXNotRecognizedException(name); + } + } + } + } + + /** + * This will look up the value of a SAX feature. + * + * @param name + * String the feature name, which is a fully-qualified + * URI. + * @return boolean the current state of the feature (true or + * false). + * @throws SAXNotRecognizedException + * when SAXOutputter does not recognize the feature name. + * @throws SAXNotSupportedException + * when SAXOutputter recognizes the feature name but determine its + * value at this time. + */ + public boolean getFeature(String name) throws SAXNotRecognizedException, + SAXNotSupportedException { + if (SAX_FEATURE_NAMESPACE_PREFIXES.equals(name)) { + // Namespace prefix declarations. + return (this.declareNamespaces); + } + if (SAX_FEATURE_NAMESPACES.equals(name)) { + // Namespaces feature always supported by SAXOutputter. + return (true); + } + if (SAX_FEATURE_VALIDATION.equals(name)) { + // Report DTD events. + return (this.reportDtdEvents); + } + // Not a supported feature. + throw new SAXNotRecognizedException(name); + } + + /** + * This will set the value of a SAX property. This method is also the + * standard mechanism for setting extended handlers. + *

+ * SAXOutputter currently supports the following SAX properties: + *

+ *
http://xml.org/sax/properties/lexical-handler
+ *
data type: + * org.xml.sax.ext.LexicalHandler
+ *
description: An optional extension handler for + * lexical events like comments.
+ *
access: read/write
+ *
http://xml.org/sax/properties/declaration-handler
+ *
data type: org.xml.sax.ext.DeclHandler
+ *
description: An optional extension handler for + * DTD-related events other than notations and unparsed entities.
+ *
access: read/write
+ *
+ *

+ * + * @param name + * String the property name, which is a fully-qualified + * URI. + * @param value + * Object the requested value for the property. + * @throws SAXNotRecognizedException + * when SAXOutputter does not recognize the property name. + * @throws SAXNotSupportedException + * when SAXOutputter recognizes the property name but cannot set the + * requested value. + */ + public void setProperty(String name, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException { + if ((SAX_PROPERTY_LEXICAL_HANDLER.equals(name)) + || (SAX_PROPERTY_LEXICAL_HANDLER_ALT.equals(name))) { + this.setLexicalHandler((LexicalHandler) value); + } else { + if ((SAX_PROPERTY_DECLARATION_HANDLER.equals(name)) + || (SAX_PROPERTY_DECLARATION_HANDLER_ALT.equals(name))) { + this.setDeclHandler((DeclHandler) value); + } else { + throw new SAXNotRecognizedException(name); + } + } + } + + /** + * This will look up the value of a SAX property. + * + * @param name + * String the property name, which is a fully-qualified + * URI. + * @return Object the current value of the property. + * @throws SAXNotRecognizedException + * when SAXOutputter does not recognize the property name. + * @throws SAXNotSupportedException + * when SAXOutputter recognizes the property name but cannot + * determine its value at this time. + */ + public Object getProperty(String name) throws SAXNotRecognizedException, + SAXNotSupportedException { + if ((SAX_PROPERTY_LEXICAL_HANDLER.equals(name)) + || (SAX_PROPERTY_LEXICAL_HANDLER_ALT.equals(name))) { + return this.getLexicalHandler(); + } + if ((SAX_PROPERTY_DECLARATION_HANDLER.equals(name)) + || (SAX_PROPERTY_DECLARATION_HANDLER_ALT.equals(name))) { + return this.getDeclHandler(); + } + throw new SAXNotRecognizedException(name); + } + + /** + * Get the current {@link SAXOutputProcessor} being used for output. + * + * @return The current SAXOutputProcessor + */ + public SAXOutputProcessor getSAXOutputProcessor() { + return processor; + } + + /** + * Set the current {@link SAXOutputProcessor} to be used for output. + * + * @param processor + * the new SAXOutputProcessor + */ + public void setSAXOutputProcessor(SAXOutputProcessor processor) { + this.processor = processor == null ? DEFAULT_PROCESSOR : processor; + } + + /** + * Get the current {@link Format} being used for output + * + * @return the current Format + */ + public Format getFormat() { + return format; + } + + /** + * Set the current {@link Format} to be used for output. + * + * @param format + * the new Format + */ + public void setFormat(Format format) { + this.format = format == null ? Format.getRawFormat() : format; + } + + private final SAXTarget buildTarget(Document doc) { + String publicID = null; + String systemID = null; + if (doc != null) { + DocType dt = doc.getDocType(); + if (dt != null) { + publicID = dt.getPublicID(); + systemID = dt.getSystemID(); + } + } + return new SAXTarget(contentHandler, errorHandler, dtdHandler, + entityResolver, lexicalHandler, declHandler, declareNamespaces, + reportDtdEvents, publicID, systemID); + } + + /** + * This will output the JDOM Document, firing off the SAX + * events that have been registered. + * + * @param document + * JDOM Document to output. + * @throws JDOMException + * if any error occurred. + */ + public void output(Document document) throws JDOMException { + processor.process(buildTarget(document), format, document); + } + + /** + * This will output a list of JDOM nodes as a document, firing off the SAX + * events that have been registered. + *

+ * Warning: This method may output ill-formed XML documents + * if the list contains top-level objects that are not legal at the document + * level (e.g. Text or CDATA nodes, multiple Element nodes, etc.). Thus, it + * should only be used to output document portions towards ContentHandlers + * capable of accepting such ill-formed documents (such as XSLT processors). + *

+ * + * @param nodes + * List of JDOM nodes to output. + * @throws JDOMException + * if any error occurred. + * @see #output(org.jdom.Document) + */ + public void output(List nodes) throws JDOMException { + processor.processAsDocument(buildTarget(null), format, nodes); + } + + /** + * This will output a single JDOM element as a document, firing off the SAX + * events that have been registered. + * + * @param node + * the Element node to output. + * @throws JDOMException + * if any error occurred. + */ + public void output(Element node) throws JDOMException { + processor.processAsDocument(buildTarget(null), format, node); + } + + /** + * This will output a list of JDOM nodes as a fragment of an XML document, + * firing off the SAX events that have been registered. + *

+ * Warning: This method does not call the + * {@link ContentHandler#setDocumentLocator}, + * {@link ContentHandler#startDocument} and + * {@link ContentHandler#endDocument} callbacks on the + * {@link #setContentHandler ContentHandler}. The user shall invoke these + * methods directly prior/after outputting the document fragments. + *

+ * + * @param nodes + * List of JDOM nodes to output. + * @throws JDOMException + * if any error occurred. + * @see #outputFragment(org.jdom.Content) + */ + public void outputFragment(List nodes) + throws JDOMException { + if (nodes == null) { + return; + } + processor.process(buildTarget(null), format, nodes); + } + + /** + * This will output a single JDOM nodes as a fragment of an XML document, + * firing off the SAX events that have been registered. + *

+ * Warning: This method does not call the + * {@link ContentHandler#setDocumentLocator}, + * {@link ContentHandler#startDocument} and + * {@link ContentHandler#endDocument} callbacks on the + * {@link #setContentHandler ContentHandler}. The user shall invoke these + * methods directly prior/after outputting the document fragments. + *

+ * + * @param node + * the Content node to output. + * @throws JDOMException + * if any error occurred. + * @see #outputFragment(java.util.List) + */ + public void outputFragment(Content node) throws JDOMException { + if (node == null) { + return; + } + + SAXTarget out = buildTarget(null); + + switch (node.getCType()) { + case CDATA: + processor.process(out, format, (CDATA) node); + break; + case Comment: + processor.process(out, format, (Comment) node); + break; + case Element: + processor.process(out, format, (Element) node); + break; + case EntityRef: + processor.process(out, format, (EntityRef) node); + break; + case ProcessingInstruction: + processor.process(out, format, (ProcessingInstruction) node); + break; + case Text: + processor.process(out, format, (Text) node); + break; + default: + handleError(new JDOMException("Invalid element content: " + node)); + } + + } + + /** + *

+ * Notifies the registered {@link ErrorHandler SAX error handler} + * (if any) of an input processing error. The error handler can + * choose to absorb the error and let the processing continue. + *

+ * + * @param exception JDOMException containing the + * error information; will be wrapped in a + * {@link SAXParseException} when reported to + * the SAX error handler. + * + * @throws JDOMException if no error handler has been registered + * or if the error handler fired a + * {@link SAXException}. + */ + private void handleError(JDOMException exception) throws JDOMException { + if (errorHandler != null) { + try { + errorHandler.error(new SAXParseException( + exception.getMessage(), null, exception)); + } + catch (SAXException se) { + if (se.getException() instanceof JDOMException) { + throw (JDOMException)(se.getException()); + } + throw new JDOMException(se.getMessage(), se); + } + } + else { + throw exception; + } + } + + + /** + * Returns null. + * + * @return null + * @deprecated there is no way to get a meaningful document Locator outside + * of an active output process, and the contents of the locator + * are meaningless outside of an active output process anyway. + */ + @Deprecated + public JDOMLocator getLocator() { + return null; + } +} diff --git a/core/src/java/org/jdom/output/StAXEventOutputter.java b/core/src/java/org/jdom/output/StAXEventOutputter.java new file mode 100644 index 0000000..04d70ee --- /dev/null +++ b/core/src/java/org/jdom/output/StAXEventOutputter.java @@ -0,0 +1,579 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import java.util.List; + +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.util.XMLEventConsumer; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.support.AbstractStAXEventProcessor; +import org.jdom.output.support.StAXEventProcessor; +import org.jdom.output.support.XMLOutputProcessor; + +/** + * Outputs a JDOM document as a StAX XMLEventConsumer of bytes. + *

+ * The StAXStreamOutputter can manage many styles of document formatting, from + * untouched to 'pretty' printed. The default is to output the document content + * exactly as created, but this can be changed by setting a new Format object: + *

    + *
  • For pretty-print output, use + * {@link Format#getPrettyFormat()}. + *
  • For whitespace-normalised output, use + * {@link Format#getCompactFormat()}. + *
  • For unmodified-format output, use + * {@link Format#getRawFormat()}. + *
+ *

+ * All of the output*(...) methods will flush the + * destination XMLEventConsumer before returning, and none of them + * will close() the destination. + *

+ * To omit output of the declaration use + * {@link Format#setOmitDeclaration}. To omit printing of the + * encoding in the declaration use {@link Format#setOmitEncoding}. + *

+ * If changing the {@link Format} settings are insufficient for your output + * needs you can customise this StAXStreamOutputter further by setting a different + * {@link StAXEventProcessor} with the + * {@link #setStAXEventProcessor(StAXEventProcessor)} method or an appropriate + * constructor. A fully-enabled Abstract class + * {@link AbstractStAXEventProcessor} is available to be further extended to + * your needs if all you want to do is tweak some details. + * + * @since JDOM2 + * @author Rolf Lear + */ + +public final class StAXEventOutputter implements Cloneable { + + /* + * ===================================================================== + * Static content. + * ===================================================================== + */ + + /** + * Create a final and static instance of the AbstractStAXEventProcessor The + * final part is important because it improves performance. + *

+ * The JDOM user can change the actual XMLOutputProcessor with the + * {@link StAXEventOutputter#setStAXEventProcessor(StAXEventProcessor)} method. + * + * @author rolf + */ + private static final class DefaultStAXEventProcessor + extends AbstractStAXEventProcessor { + // nothing + } + + /** + * This constant StAXEventProcessor is used for all non-customised + * StAXStreamOutputters + */ + private static final DefaultStAXEventProcessor DEFAULTPROCESSOR = + new DefaultStAXEventProcessor(); + + /** + * This constant StAXEventProcessor is used for all non-customised + * StAXStreamOutputters + */ + private static final XMLEventFactory DEFAULTEVENTFACTORY = + XMLEventFactory.newInstance(); + + /* + * ===================================================================== + * Instance content. + * ===================================================================== + */ + + // For normal output + private Format myFormat = null; + + // The actual StAXEventProcessor to delegate to. + private StAXEventProcessor myProcessor = null; + + private XMLEventFactory myEventFactory = null; + + /* + * ===================================================================== + * Constructors + * ===================================================================== + */ + + /** + * This will create an StAXStreamOutputter with the specified format + * characteristics. + *

+ * Note: the format object is cloned internally before use. If you + * want to modify the Format after constructing the StAXStreamOutputter you can + * modify the Format instance {@link #getFormat()} returns. + * + * @param format + * The Format instance to use. This instance will be cloned() and as + * a consequence, changes made to the specified format instance + * will not be reflected in this StAXStreamOutputter. A null input + * format indicates that StAXStreamOutputter should use the default + * {@link Format#getRawFormat()} + * @param processor + * The XMLOutputProcessor to delegate output to. If null the + * StAXStreamOutputter will use the default XMLOutputProcessor. + * @param eventfactory + * The factory to use to create XMLEvent instances. + */ + public StAXEventOutputter(Format format, StAXEventProcessor processor, XMLEventFactory eventfactory) { + myFormat = format == null ? Format.getRawFormat() : format.clone(); + myProcessor = processor == null ? DEFAULTPROCESSOR : processor; + myEventFactory = eventfactory == null ? DEFAULTEVENTFACTORY : eventfactory; + } + + /** + * This will create an StAXStreamOutputter with a default + * {@link Format} and {@link XMLOutputProcessor}. + */ + public StAXEventOutputter() { + this(null, null, null); + } + + /** + * This will create an StAXStreamOutputter with the specified format + * characteristics. + *

+ * Note: the format object is cloned internally before use. + * + * @param format + * The Format instance to use. This instance will be cloned() and as + * a consequence, changes made to the specified format instance + * will not be reflected in this StAXStreamOutputter. A null input + * format indicates that StAXStreamOutputter should use the default + * {@link Format#getRawFormat()} + */ + public StAXEventOutputter(Format format) { + this(format, null, null); + } + + /** + * This will create an StAXStreamOutputter with the specified + * XMLOutputProcessor. + * + * @param processor + * The XMLOutputProcessor to delegate output to. If null the + * StAXStreamOutputter will use the default XMLOutputProcessor. + */ + public StAXEventOutputter(StAXEventProcessor processor) { + this(null, processor, null); + } + + /** + * This will create an StAXStreamOutputter with the specified + * XMLOutputProcessor. + * + * @param eventfactory + * The XMLEventFactory to use to create XMLEvent instances. + */ + public StAXEventOutputter(XMLEventFactory eventfactory) { + this(null, null, eventfactory); + } + + /* + * ======================================================================= + * API - Settings... + * ======================================================================= + */ + + /** + * Sets the new format logic for the StAXStreamOutputter. Note the Format object is + * cloned internally before use. + * + * @see #getFormat() + * @param newFormat + * the format to use for subsequent output + */ + public void setFormat(Format newFormat) { + this.myFormat = newFormat.clone(); + } + + /** + * Returns the current format in use by the StAXStreamOutputter. Note the Format + * object returned is not a clone of the one used internally, thus, + * an StAXStreamOutputter instance is able to have it's Format changed by changing + * the settings on the Format instance returned by this method. + * + * @return the current Format instance used by this StAXStreamOutputter. + */ + public Format getFormat() { + return myFormat; + } + + /** + * Returns the current XMLOutputProcessor instance in use by the + * StAXStreamOutputter. + * + * @return the current XMLOutputProcessor instance. + */ + public StAXEventProcessor getStAXStream() { + return myProcessor; + } + + /** + * Sets a new XMLOutputProcessor instance for this StAXStreamOutputter. Note the + * processor object is expected to be thread-safe. + * + * @param processor + * the new XMLOutputProcesor to use for output + */ + public void setStAXEventProcessor(StAXEventProcessor processor) { + this.myProcessor = processor; + } + + + + /** + * @return the current XMLEventFactory used by this StAXEventOutputter + */ + public XMLEventFactory getEventFactory() { + return myEventFactory; + } + + /** + * @param myEventFactory the XMLEventFactory to use for subsequent output. + */ + public void setEventFactory(XMLEventFactory myEventFactory) { + this.myEventFactory = myEventFactory; + } + + /* + * ======================================================================== + * API - Output to XMLEventConsumer Methods ... These are the core methods that the + * Stream and String output methods call. On the other hand, these methods + * defer to the protected/override methods. These methods flush the writer. + * ======================================================================== + */ + + /** + * This will print the Document to the given Writer. + *

+ * Warning: using your own Writer may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param doc + * Document to format. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Document doc, XMLEventConsumer out) throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, doc); + //out.flush(); + } + + /** + * Print out the {@link DocType}. + * + * @param doctype + * DocType to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(DocType doctype, XMLEventConsumer out) throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, doctype); + //out.flush(); + } + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param element + * Element to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Element element, XMLEventConsumer out) throws XMLStreamException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + myProcessor.process(out, myFormat, myEventFactory, element); + //out.flush(); + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and attributes. + * This can be useful for printing the content of an element that contains + * HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param element + * Element to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void outputElementContent(Element element, XMLEventConsumer out) + throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, element.getContent()); + //out.flush(); + } + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param list + * List of nodes. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(List list, XMLEventConsumer out) + throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, list); + //out.flush(); + } + + /** + * Print out a {@link CDATA} node. + * + * @param cdata + * CDATA to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(CDATA cdata, XMLEventConsumer out) throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, cdata); + //out.flush(); + } + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param text + * Text to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Text text, XMLEventConsumer out) throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, text); + //out.flush(); + } + + /** + * Print out a {@link Comment}. + * + * @param comment + * Comment to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Comment comment, XMLEventConsumer out) throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, comment); + //out.flush(); + } + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param pi + * ProcessingInstruction to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(ProcessingInstruction pi, XMLEventConsumer out) + throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, pi); + //out.flush(); + } + + /** + * Print out an {@link EntityRef}. + * + * @param entity + * EntityRef to output. + * @param out + * XMLEventConsumer to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(EntityRef entity, XMLEventConsumer out) throws XMLStreamException { + myProcessor.process(out, myFormat, myEventFactory, entity); + //out.flush(); + } + + /* + * ======================================================================== + * Basic Support methods. + * ======================================================================== + */ + + /** + * Returns a cloned copy of this StAXStreamOutputter. + */ + @Override + public StAXEventOutputter clone() { + // Implementation notes: Since all state of an StAXStreamOutputter is + // embodied in simple private instance variables, Object.clone + // can be used. Note that since Object.clone is totally + // broken, we must catch an exception that will never be + // thrown. + try { + return (StAXEventOutputter) super.clone(); + } catch (java.lang.CloneNotSupportedException e) { + // even though this should never ever happen, it's still + // possible to fool Java into throwing a + // CloneNotSupportedException. If that happens, we + // shouldn't swallow it. + throw new RuntimeException(e.toString()); + } + } + + /** + * Return a string listing of the settings for this StAXStreamOutputter instance. + * + * @return a string listing the settings for this StAXStreamOutputter instance + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("StAXStreamOutputter[omitDeclaration = "); + buffer.append(myFormat.omitDeclaration); + buffer.append(", "); + buffer.append("encoding = "); + buffer.append(myFormat.encoding); + buffer.append(", "); + buffer.append("omitEncoding = "); + buffer.append(myFormat.omitEncoding); + buffer.append(", "); + buffer.append("indent = '"); + buffer.append(myFormat.indent); + buffer.append("'"); + buffer.append(", "); + buffer.append("expandEmptyElements = "); + buffer.append(myFormat.expandEmptyElements); + buffer.append(", "); + buffer.append("lineSeparator = '"); + for (char ch : myFormat.lineSeparator.toCharArray()) { + switch (ch) { + case '\r': + buffer.append("\\r"); + break; + case '\n': + buffer.append("\\n"); + break; + case '\t': + buffer.append("\\t"); + break; + default: + buffer.append("[" + ((int) ch) + "]"); + break; + } + } + buffer.append("', "); + buffer.append("textMode = "); + buffer.append(myFormat.mode + "]"); + return buffer.toString(); + } + +} diff --git a/core/src/java/org/jdom/output/StAXStreamOutputter.java b/core/src/java/org/jdom/output/StAXStreamOutputter.java new file mode 100644 index 0000000..14d939a --- /dev/null +++ b/core/src/java/org/jdom/output/StAXStreamOutputter.java @@ -0,0 +1,539 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import java.util.List; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.support.AbstractStAXStreamProcessor; +import org.jdom.output.support.StAXStreamProcessor; +import org.jdom.output.support.XMLOutputProcessor; + +/** + * Outputs a JDOM document as a StAX XMLStreamWriter of bytes. + *

+ * The StAXStreamOutputter can manage many styles of document formatting, from + * untouched to 'pretty' printed. The default is to output the document content + * exactly as created, but this can be changed by setting a new Format object: + *

    + *
  • For pretty-print output, use + * {@link Format#getPrettyFormat()}. + *
  • For whitespace-normalised output, use + * {@link Format#getCompactFormat()}. + *
  • For unmodified-format output, use + * {@link Format#getRawFormat()}. + *
+ *

+ * All of the output*(...) methods will flush the + * destination XMLStreamWriter before returning, and none of them + * will close() the destination. + *

+ * To omit output of the declaration use + * {@link Format#setOmitDeclaration}. To omit printing of the + * encoding in the declaration use {@link Format#setOmitEncoding}. + *

+ * If changing the {@link Format} settings are insufficient for your output + * needs you can customise this StAXStreamOutputter further by setting a different + * {@link StAXStreamProcessor} with the + * {@link #setStAXStreamProcessor(StAXStreamProcessor)} method or an appropriate + * constructor. A fully-enabled Abstract class + * {@link AbstractStAXStreamProcessor} is available to be further extended to + * your needs if all you want to do is tweak some details. + * + * @since JDOM2 + * @author Rolf Lear + */ + +public final class StAXStreamOutputter implements Cloneable { + + /* + * ===================================================================== + * Static content. + * ===================================================================== + */ + + /** + * Create a final and static instance of the AbstractStAXStreamProcessor The + * final part is important because it improves performance. + *

+ * The JDOM user can change the actual XMLOutputProcessor with the + * {@link StAXStreamOutputter#setStAXStreamProcessor(StAXStreamProcessor)} method. + * + * @author rolf + */ + private static final class DefaultStAXStreamProcessor + extends AbstractStAXStreamProcessor { + // nothing + } + + /** + * This constant StAXStreamProcessor is used for all non-customised + * StAXStreamOutputters + */ + private static final DefaultStAXStreamProcessor DEFAULTPROCESSOR = + new DefaultStAXStreamProcessor(); + + /* + * ===================================================================== + * Instance content. + * ===================================================================== + */ + + // For normal output + private Format myFormat = null; + + // The actual StAXStreamProcessor to delegate to. + private StAXStreamProcessor myProcessor = null; + + /* + * ===================================================================== + * Constructors + * ===================================================================== + */ + + /** + * This will create an StAXStreamOutputter with the specified format + * characteristics. + *

+ * Note: the format object is cloned internally before use. If you + * want to modify the Format after constructing the StAXStreamOutputter you can + * modify the Format instance {@link #getFormat()} returns. + * + * @param format + * The Format instance to use. This instance will be cloned() and as + * a consequence, changes made to the specified format instance + * will not be reflected in this StAXStreamOutputter. A null input + * format indicates that StAXStreamOutputter should use the default + * {@link Format#getRawFormat()} + * @param processor + * The XMLOutputProcessor to delegate output to. If null the + * StAXStreamOutputter will use the default XMLOutputProcessor. + */ + public StAXStreamOutputter(Format format, StAXStreamProcessor processor) { + myFormat = format == null ? Format.getRawFormat() : format.clone(); + myProcessor = processor == null ? DEFAULTPROCESSOR : processor; + } + + /** + * This will create an StAXStreamOutputter with a default + * {@link Format} and {@link XMLOutputProcessor}. + */ + public StAXStreamOutputter() { + this(null, null); + } + + /** + * This will create an StAXStreamOutputter with the specified format + * characteristics. + *

+ * Note: the format object is cloned internally before use. + * + * @param format + * The Format instance to use. This instance will be cloned() and as + * a consequence, changes made to the specified format instance + * will not be reflected in this StAXStreamOutputter. A null input + * format indicates that StAXStreamOutputter should use the default + * {@link Format#getRawFormat()} + */ + public StAXStreamOutputter(Format format) { + this(format, null); + } + + /** + * This will create an StAXStreamOutputter with the specified + * XMLOutputProcessor. + * + * @param processor + * The XMLOutputProcessor to delegate output to. If null the + * StAXStreamOutputter will use the default XMLOutputProcessor. + */ + public StAXStreamOutputter(StAXStreamProcessor processor) { + this(null, processor); + } + + /* + * ======================================================================= + * API - Settings... + * ======================================================================= + */ + + /** + * Sets the new format logic for the StAXStreamOutputter. Note the Format object is + * cloned internally before use. + * + * @see #getFormat() + * @param newFormat + * the format to use for subsequent output + */ + public void setFormat(Format newFormat) { + this.myFormat = newFormat.clone(); + } + + /** + * Returns the current format in use by the StAXStreamOutputter. Note the Format + * object returned is not a clone of the one used internally, thus, + * an StAXStreamOutputter instance is able to have it's Format changed by changing + * the settings on the Format instance returned by this method. + * + * @return the current Format instance used by this StAXStreamOutputter. + */ + public Format getFormat() { + return myFormat; + } + + /** + * Returns the current XMLOutputProcessor instance in use by the + * StAXStreamOutputter. + * + * @return the current XMLOutputProcessor instance. + */ + public StAXStreamProcessor getStAXStream() { + return myProcessor; + } + + /** + * Sets a new XMLOutputProcessor instance for this StAXStreamOutputter. Note the + * processor object is expected to be thread-safe. + * + * @param processor + * the new XMLOutputProcesor to use for output + */ + public void setStAXStreamProcessor(StAXStreamProcessor processor) { + this.myProcessor = processor; + } + + /* + * ======================================================================== + * API - Output to XMLStreamWriter Methods ... These are the core methods that the + * Stream and String output methods call. On the other hand, these methods + * defer to the protected/override methods. These methods flush the writer. + * ======================================================================== + */ + + /** + * This will print the Document to the given Writer. + *

+ * Warning: using your own Writer may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param doc + * Document to format. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Document doc, XMLStreamWriter out) throws XMLStreamException { + myProcessor.process(out, myFormat, doc); + out.flush(); + } + + /** + * Print out the {@link DocType}. + * + * @param doctype + * DocType to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(DocType doctype, XMLStreamWriter out) throws XMLStreamException { + myProcessor.process(out, myFormat, doctype); + out.flush(); + } + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param element + * Element to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Element element, XMLStreamWriter out) throws XMLStreamException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + myProcessor.process(out, myFormat, element); + out.flush(); + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and attributes. + * This can be useful for printing the content of an element that contains + * HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param element + * Element to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void outputElementContent(Element element, XMLStreamWriter out) + throws XMLStreamException { + myProcessor.process(out, myFormat, element.getContent()); + out.flush(); + } + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param list + * List of nodes. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(List list, XMLStreamWriter out) + throws XMLStreamException { + myProcessor.process(out, myFormat, list); + out.flush(); + } + + /** + * Print out a {@link CDATA} node. + * + * @param cdata + * CDATA to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(CDATA cdata, XMLStreamWriter out) throws XMLStreamException { + myProcessor.process(out, myFormat, cdata); + out.flush(); + } + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param text + * Text to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Text text, XMLStreamWriter out) throws XMLStreamException { + myProcessor.process(out, myFormat, text); + out.flush(); + } + + /** + * Print out a {@link Comment}. + * + * @param comment + * Comment to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Comment comment, XMLStreamWriter out) throws XMLStreamException { + myProcessor.process(out, myFormat, comment); + out.flush(); + } + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param pi + * ProcessingInstruction to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(ProcessingInstruction pi, XMLStreamWriter out) + throws XMLStreamException { + myProcessor.process(out, myFormat, pi); + out.flush(); + } + + /** + * Print out an {@link EntityRef}. + * + * @param entity + * EntityRef to output. + * @param out + * XMLStreamWriter to use. + * @throws XMLStreamException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(EntityRef entity, XMLStreamWriter out) throws XMLStreamException { + myProcessor.process(out, myFormat, entity); + out.flush(); + } + + /* + * ======================================================================== + * Basic Support methods. + * ======================================================================== + */ + + /** + * Returns a cloned copy of this StAXStreamOutputter. + */ + @Override + public StAXStreamOutputter clone() { + // Implementation notes: Since all state of an StAXStreamOutputter is + // embodied in simple private instance variables, Object.clone + // can be used. Note that since Object.clone is totally + // broken, we must catch an exception that will never be + // thrown. + try { + return (StAXStreamOutputter) super.clone(); + } catch (java.lang.CloneNotSupportedException e) { + // even though this should never ever happen, it's still + // possible to fool Java into throwing a + // CloneNotSupportedException. If that happens, we + // shouldn't swallow it. + throw new RuntimeException(e.toString()); + } + } + + /** + * Return a string listing of the settings for this StAXStreamOutputter instance. + * + * @return a string listing the settings for this StAXStreamOutputter instance + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("StAXStreamOutputter[omitDeclaration = "); + buffer.append(myFormat.omitDeclaration); + buffer.append(", "); + buffer.append("encoding = "); + buffer.append(myFormat.encoding); + buffer.append(", "); + buffer.append("omitEncoding = "); + buffer.append(myFormat.omitEncoding); + buffer.append(", "); + buffer.append("indent = '"); + buffer.append(myFormat.indent); + buffer.append("'"); + buffer.append(", "); + buffer.append("expandEmptyElements = "); + buffer.append(myFormat.expandEmptyElements); + buffer.append(", "); + buffer.append("lineSeparator = '"); + for (char ch : myFormat.lineSeparator.toCharArray()) { + switch (ch) { + case '\r': + buffer.append("\\r"); + break; + case '\n': + buffer.append("\\n"); + break; + case '\t': + buffer.append("\\t"); + break; + default: + buffer.append("[" + ((int) ch) + "]"); + break; + } + } + buffer.append("', "); + buffer.append("textMode = "); + buffer.append(myFormat.mode + "]"); + return buffer.toString(); + } + +} diff --git a/core/src/java/org/jdom/output/XMLOutputter.java b/core/src/java/org/jdom/output/XMLOutputter.java new file mode 100644 index 0000000..d202a97 --- /dev/null +++ b/core/src/java/org/jdom/output/XMLOutputter.java @@ -0,0 +1,1651 @@ +/*-- + + $Id: XMLOutputter.java,v 1.117 2009/07/23 05:54:23 jhunter Exp $ + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import java.io.*; +import java.util.*; + +import javax.xml.transform.Result; + +import org.jdom.*; + +/** + * Outputs a JDOM document as a stream of bytes. The outputter can manage many + * styles of document formatting, from untouched to pretty printed. The default + * is to output the document content exactly as created, but this can be changed + * by setting a new Format object. For pretty-print output, use + * {@link Format#getPrettyFormat()}. For whitespace-normalized + * output, use {@link Format#getCompactFormat()}. + *

+ * There are {@link #output output(...)} methods to print any of + * the standard JDOM classes, including Document and Element, to either a Writer + * or an OutputStream. Warning: When outputting to a Writer, make sure + * the writer's encoding matches the encoding setting in the Format object. This + * ensures the encoding in which the content is written (controlled by the + * Writer configuration) matches the encoding placed in the document's XML + * declaration (controlled by the XMLOutputter). Because a Writer cannot be + * queried for its encoding, the information must be passed to the Format + * manually in its constructor or via the + * {@link Format#setEncoding} method. The default encoding is + * UTF-8. + *

+ * The methods {@link #outputString outputString(...)} are for + * convenience only; for top performance you should call one of the {@link + * #output output(...)} methods and pass in your own Writer or + * OutputStream if possible. + *

+ * XML declarations are always printed on their own line followed by a line + * seperator (this doesn't change the semantics of the document). To omit + * printing of the declaration use + * {@link Format#setOmitDeclaration}. To omit printing of the + * encoding in the declaration use {@link Format#setOmitEncoding}. + * Unfortunatly there is currently no way to know the original encoding of the + * document. + *

+ * Empty elements are by default printed as <empty/>, but this can be + * configured with {@link Format#setExpandEmptyElements} to cause + * them to be expanded to <empty></empty>. + * + * @version $Revision: 1.117 $, $Date: 2009/07/23 05:54:23 $ + * @author Brett McLaughlin + * @author Jason Hunter + * @author Jason Reid + * @author Wolfgang Werner + * @author Elliotte Rusty Harold + * @author David & Will (from Post Tool Design) + * @author Dan Schaffer + * @author Alex Chaffee + * @author Bradley S. Huffman + */ + +public class XMLOutputter implements Cloneable { + + private static final String CVS_ID = + "@(#) $RCSfile: XMLOutputter.java,v $ $Revision: 1.117 $ $Date: 2009/07/23 05:54:23 $ $Name: $"; + + // For normal output + private Format userFormat = Format.getRawFormat(); + + // For xml:space="preserve" + protected static final Format preserveFormat = Format.getRawFormat(); + + // What's currently in use + protected Format currentFormat = userFormat; + + /** Whether output escaping is enabled for the being processed + * Element - default is true */ + private boolean escapeOutput = true; + + // * * * * * * * * * * Constructors * * * * * * * * * * + // * * * * * * * * * * Constructors * * * * * * * * * * + + /** + * This will create an XMLOutputter with the default + * {@link Format} matching {@link Format#getRawFormat}. + */ + public XMLOutputter() { + } + + /** + * This will create an XMLOutputter with the specified + * format characteristics. Note the format object is cloned internally + * before use. + */ + public XMLOutputter(Format format) { + userFormat = (Format) format.clone(); + currentFormat = userFormat; + } + + /** + * This will create an XMLOutputter with all the + * options as set in the given XMLOutputter. Note + * that XMLOutputter two = (XMLOutputter)one.clone(); + * would work equally well. + * + * @param that the XMLOutputter to clone + */ + public XMLOutputter(XMLOutputter that) { + this.userFormat = (Format) that.userFormat.clone(); + currentFormat = userFormat; + } + + // * * * * * * * * * * Set parameters methods * * * * * * * * * * + // * * * * * * * * * * Set parameters methods * * * * * * * * * * + + /** + * Sets the new format logic for the outputter. Note the Format + * object is cloned internally before use. + * + * @param newFormat the format to use for output + */ + public void setFormat(Format newFormat) { + this.userFormat = (Format) newFormat.clone(); + this.currentFormat = userFormat; + } + + /** + * Returns the current format in use by the outputter. Note the + * Format object returned is a clone of the one used internally. + */ + public Format getFormat() { + return (Format) userFormat.clone(); + } + + // * * * * * * * * * * Output to a OutputStream * * * * * * * * * * + // * * * * * * * * * * Output to a OutputStream * * * * * * * * * * + + /** + * This will print the Document to the given output stream. + * The characters are printed using the encoding specified in the + * constructor, or a default of UTF-8. + * + * @param doc Document to format. + * @param out OutputStream to use. + * @throws IOException - if there's any problem writing. + */ + public void output(Document doc, OutputStream out) + throws IOException { + Writer writer = makeWriter(out); + output(doc, writer); // output() flushes + } + + /** + * Print out the {@link DocType}. + * + * @param doctype DocType to output. + * @param out OutputStream to use. + */ + public void output(DocType doctype, OutputStream out) throws IOException { + Writer writer = makeWriter(out); + output(doctype, writer); // output() flushes + } + + /** + * Print out an {@link Element}, including + * its {@link Attribute}s, and all + * contained (child) elements, etc. + * + * @param element Element to output. + * @param out Writer to use. + */ + public void output(Element element, OutputStream out) throws IOException { + Writer writer = makeWriter(out); + output(element, writer); // output() flushes + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and + * attributes. This can be useful for printing the content of an + * element that contains HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param element Element to output. + * @param out OutputStream to use. + */ + public void outputElementContent(Element element, OutputStream out) + throws IOException { + Writer writer = makeWriter(out); + outputElementContent(element, writer); // output() flushes + } + + /** + * This will handle printing out a list of nodes. + * This can be useful for printing the content of an element that + * contains HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param list List of nodes. + * @param out OutputStream to use. + */ + public void output(List list, OutputStream out) + throws IOException { + Writer writer = makeWriter(out); + output(list, writer); // output() flushes + } + + /** + * Print out a {@link CDATA} node. + * + * @param cdata CDATA to output. + * @param out OutputStream to use. + */ + public void output(CDATA cdata, OutputStream out) throws IOException { + Writer writer = makeWriter(out); + output(cdata, writer); // output() flushes + } + + /** + * Print out a {@link Text} node. Perfoms + * the necessary entity escaping and whitespace stripping. + * + * @param text Text to output. + * @param out OutputStream to use. + */ + public void output(Text text, OutputStream out) throws IOException { + Writer writer = makeWriter(out); + output(text, writer); // output() flushes + } + + /** + * Print out a {@link Comment}. + * + * @param comment Comment to output. + * @param out OutputStream to use. + */ + public void output(Comment comment, OutputStream out) throws IOException { + Writer writer = makeWriter(out); + output(comment, writer); // output() flushes + } + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param pi ProcessingInstruction to output. + * @param out OutputStream to use. + */ + public void output(ProcessingInstruction pi, OutputStream out) + throws IOException { + Writer writer = makeWriter(out); + output(pi, writer); // output() flushes + } + + /** + * Print out a {@link EntityRef}. + * + * @param entity EntityRef to output. + * @param out OutputStream to use. + */ + public void output(EntityRef entity, OutputStream out) throws IOException { + Writer writer = makeWriter(out); + output(entity, writer); // output() flushes + } + + /** + * Get an OutputStreamWriter, using prefered encoding + * (see {@link Format#setEncoding}). + */ + private Writer makeWriter(OutputStream out) + throws java.io.UnsupportedEncodingException { + return makeWriter(out, userFormat.encoding); + } + + /** + * Get an OutputStreamWriter, use specified encoding. + */ + private static Writer makeWriter(OutputStream out, String enc) + throws java.io.UnsupportedEncodingException { + // "UTF-8" is not recognized before JDK 1.1.6, so we'll translate + // into "UTF8" which works with all JDKs. + if ("UTF-8".equals(enc)) { + enc = "UTF8"; + } + + Writer writer = new BufferedWriter( + (new OutputStreamWriter( + new BufferedOutputStream(out), enc) + )); + return writer; + } + + // * * * * * * * * * * Output to a Writer * * * * * * * * * * + // * * * * * * * * * * Output to a Writer * * * * * * * * * * + + /** + * This will print the Document to the given Writer. + * + *

+ * Warning: using your own Writer may cause the outputter's + * preferred character encoding to be ignored. If you use + * encodings other than UTF-8, we recommend using the method that + * takes an OutputStream instead. + *

+ * + * @param doc Document to format. + * @param out Writer to use. + * @throws IOException - if there's any problem writing. + */ + public void output(Document doc, Writer out) throws IOException { + + printDeclaration(out, doc, userFormat.encoding); + + // Print out root element, as well as any root level + // comments and processing instructions, + // starting with no indentation + List content = doc.getContent(); + int size = content.size(); + for (int i = 0; i < size; i++) { + Object obj = content.get(i); + + if (obj instanceof Element) { + printElement(out, doc.getRootElement(), 0, + createNamespaceStack()); + } + else if (obj instanceof Comment) { + printComment(out, (Comment) obj); + } + else if (obj instanceof ProcessingInstruction) { + printProcessingInstruction(out, (ProcessingInstruction) obj); + } + else if (obj instanceof DocType) { + printDocType(out, doc.getDocType()); + // Always print line separator after declaration, helps the + // output look better and is semantically inconsequential + writeLineSeparator(out); + } + else { + // XXX if we get here then we have a illegal content, for + // now we'll just ignore it + } + + newline(out); + indent(out, 0); + } + + // Output final line separator + // We output this no matter what the newline flags say + writeLineSeparator(out); + + out.flush(); + } + + private void writeLineSeparator(Writer out) throws IOException { + if (currentFormat.lineSeparator != null) { + out.write(currentFormat.lineSeparator); + } + } + + /** + * Print out the {@link DocType}. + * + * @param doctype DocType to output. + * @param out Writer to use. + */ + public void output(DocType doctype, Writer out) throws IOException { + printDocType(out, doctype); + out.flush(); + } + + /** + * Print out an {@link Element}, including + * its {@link Attribute}s, and all + * contained (child) elements, etc. + * + * @param element Element to output. + * @param out Writer to use. + */ + public void output(Element element, Writer out) throws IOException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + printElement(out, element, 0, createNamespaceStack()); + out.flush(); + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and + * attributes. This can be useful for printing the content of an + * element that contains HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param element Element to output. + * @param out Writer to use. + */ + public void outputElementContent(Element element, Writer out) + throws IOException { + List content = element.getContent(); + printContentRange(out, content, 0, content.size(), + 0, createNamespaceStack()); + out.flush(); + } + + /** + * This will handle printing out a list of nodes. + * This can be useful for printing the content of an element that + * contains HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param list List of nodes. + * @param out Writer to use. + */ + public void output(List list, Writer out) + throws IOException { + printContentRange(out, list, 0, list.size(), + 0, createNamespaceStack()); + out.flush(); + } + + /** + * Print out a {@link CDATA} node. + * + * @param cdata CDATA to output. + * @param out Writer to use. + */ + public void output(CDATA cdata, Writer out) throws IOException { + printCDATA(out, cdata); + out.flush(); + } + + /** + * Print out a {@link Text} node. Perfoms + * the necessary entity escaping and whitespace stripping. + * + * @param text Text to output. + * @param out Writer to use. + */ + public void output(Text text, Writer out) throws IOException { + printText(out, text); + out.flush(); + } + + /** + * Print out a {@link Comment}. + * + * @param comment Comment to output. + * @param out Writer to use. + */ + public void output(Comment comment, Writer out) throws IOException { + printComment(out, comment); + out.flush(); + } + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param pi ProcessingInstruction to output. + * @param out Writer to use. + */ + public void output(ProcessingInstruction pi, Writer out) + throws IOException { + boolean currentEscapingPolicy = currentFormat.ignoreTrAXEscapingPIs; + + // Output PI verbatim, disregarding TrAX escaping PIs. + currentFormat.setIgnoreTrAXEscapingPIs(true); + printProcessingInstruction(out, pi); + currentFormat.setIgnoreTrAXEscapingPIs(currentEscapingPolicy); + + out.flush(); + } + + /** + * Print out a {@link EntityRef}. + * + * @param entity EntityRef to output. + * @param out Writer to use. + */ + public void output(EntityRef entity, Writer out) throws IOException { + printEntityRef(out, entity); + out.flush(); + } + + // * * * * * * * * * * Output to a String * * * * * * * * * * + // * * * * * * * * * * Output to a String * * * * * * * * * * + + /** + * Return a string representing a document. Uses an internal + * StringWriter. Warning: a String is Unicode, which may not match + * the outputter's specified encoding. + * + * @param doc Document to format. + */ + public String outputString(Document doc) { + StringWriter out = new StringWriter(); + try { + output(doc, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing a DocType. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param doctype DocType to format. + */ + public String outputString(DocType doctype) { + StringWriter out = new StringWriter(); + try { + output(doctype, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing an element. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param element Element to format. + */ + public String outputString(Element element) { + StringWriter out = new StringWriter(); + try { + output(element, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing a list of nodes. The list is + * assumed to contain legal JDOM nodes. + * + * @param list List to format. + */ + public String outputString(List list) { + StringWriter out = new StringWriter(); + try { + output(list, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing a CDATA node. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param cdata CDATA to format. + */ + public String outputString(CDATA cdata) { + StringWriter out = new StringWriter(); + try { + output(cdata, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing a Text node. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param text Text to format. + */ + public String outputString(Text text) { + StringWriter out = new StringWriter(); + try { + output(text, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + + /** + * Return a string representing a comment. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param comment Comment to format. + */ + public String outputString(Comment comment) { + StringWriter out = new StringWriter(); + try { + output(comment, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing a PI. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param pi ProcessingInstruction to format. + */ + public String outputString(ProcessingInstruction pi) { + StringWriter out = new StringWriter(); + try { + output(pi, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + /** + * Return a string representing an entity. Warning: a String is + * Unicode, which may not match the outputter's specified + * encoding. + * + * @param entity EntityRef to format. + */ + public String outputString(EntityRef entity) { + StringWriter out = new StringWriter(); + try { + output(entity, out); // output() flushes + } catch (IOException e) { } + return out.toString(); + } + + // * * * * * * * * * * Internal printing methods * * * * * * * * * * + // * * * * * * * * * * Internal printing methods * * * * * * * * * * + + /** + * This will handle printing of the declaration. + * Assumes XML version 1.0 since we don't directly know. + * + * @param doc Document whose declaration to write. + * @param out Writer to use. + * @param encoding The encoding to add to the declaration + */ + protected void printDeclaration(Writer out, Document doc, + String encoding) throws IOException { + + // Only print the declaration if it's not being omitted + if (!userFormat.omitDeclaration) { + // Assume 1.0 version + out.write(""); + + // Print new line after decl always, even if no other new lines + // Helps the output look better and is semantically + // inconsequential + writeLineSeparator(out); + } + } + + /** + * This handle printing the DOCTYPE declaration if one exists. + * + * @param docType Document whose declaration to write. + * @param out Writer to use. + */ + protected void printDocType(Writer out, DocType docType) + throws IOException { + + String publicID = docType.getPublicID(); + String systemID = docType.getSystemID(); + String internalSubset = docType.getInternalSubset(); + boolean hasPublic = false; + + out.write(""); + } + + /** + * This will handle printing of comments. + * + * @param comment Comment to write. + * @param out Writer to use. + */ + protected void printComment(Writer out, Comment comment) + throws IOException { + out.write(""); + } + + /** + * This will handle printing of processing instructions. + * + * @param pi ProcessingInstruction to write. + * @param out Writer to use. + */ + protected void printProcessingInstruction(Writer out, ProcessingInstruction pi + ) throws IOException { + String target = pi.getTarget(); + boolean piProcessed = false; + + if (currentFormat.ignoreTrAXEscapingPIs == false) { + if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) { + escapeOutput = false; + piProcessed = true; + } + else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) { + escapeOutput = true; + piProcessed = true; + } + } + if (piProcessed == false) { + String rawData = pi.getData(); + + // Write or if no data then just + if (!"".equals(rawData)) { + out.write(""); + } + else { + out.write(""); + } + } + } + + /** + * This will handle printing a {@link EntityRef}. + * Only the entity reference such as &entity; + * will be printed. However, subclasses are free to override + * this method to print the contents of the entity instead. + * + * @param entity EntityRef to output. + * @param out Writer to use. */ + protected void printEntityRef(Writer out, EntityRef entity) + throws IOException { + out.write("&"); + out.write(entity.getName()); + out.write(";"); + } + + /** + * This will handle printing of {@link CDATA} text. + * + * @param cdata CDATA to output. + * @param out Writer to use. + */ + protected void printCDATA(Writer out, CDATA cdata) throws IOException { + String str = (currentFormat.mode == Format.TextMode.NORMALIZE) + ? cdata.getTextNormalize() + : ((currentFormat.mode == Format.TextMode.TRIM) ? + cdata.getText().trim() : cdata.getText()); + out.write(""); + } + + /** + * This will handle printing of {@link Text} strings. + * + * @param text Text to write. + * @param out Writer to use. + */ + protected void printText(Writer out, Text text) throws IOException { + String str = (currentFormat.mode == Format.TextMode.NORMALIZE) + ? text.getTextNormalize() + : ((currentFormat.mode == Format.TextMode.TRIM) ? + text.getText().trim() : text.getText()); + out.write(escapeElementEntities(str)); + } + + /** + * This will handle printing a string. Escapes the element entities, + * trims interior whitespace, etc. if necessary. + */ + private void printString(Writer out, String str) throws IOException { + if (currentFormat.mode == Format.TextMode.NORMALIZE) { + str = Text.normalizeString(str); + } + else if (currentFormat.mode == Format.TextMode.TRIM) { + str = str.trim(); + } + out.write(escapeElementEntities(str)); + } + + /** + * This will handle printing of a {@link Element}, + * its {@link Attribute}s, and all contained (child) + * elements, etc. + * + * @param element Element to output. + * @param out Writer to use. + * @param level int level of indention. + * @param namespaces List stack of Namespaces in scope. + */ + protected void printElement(Writer out, Element element, + int level, NamespaceStack namespaces) + throws IOException { + + List attributes = element.getAttributes(); + List content = element.getContent(); + + // Check for xml:space and adjust format settings + String space = null; + if (attributes != null) { + space = element.getAttributeValue("space", + Namespace.XML_NAMESPACE); + } + + Format previousFormat = currentFormat; + + if ("default".equals(space)) { + currentFormat = userFormat; + } + else if ("preserve".equals(space)) { + currentFormat = preserveFormat; + } + + // Print the beginning of the tag plus attributes and any + // necessary namespace declarations + out.write("<"); + printQualifiedName(out, element); + + // Mark our namespace starting point + int previouslyDeclaredNamespaces = namespaces.size(); + + // Print the element's namespace, if appropriate + printElementNamespace(out, element, namespaces); + + // Print out additional namespace declarations + printAdditionalNamespaces(out, element, namespaces); + + // Print out attributes + if (attributes != null) + printAttributes(out, attributes, element, namespaces); + + // Depending on the settings (newlines, textNormalize, etc), we may + // or may not want to print all of the content, so determine the + // index of the start of the content we're interested + // in based on the current settings. + + int start = skipLeadingWhite(content, 0); + int size = content.size(); + if (start >= size) { + // Case content is empty or all insignificant whitespace + if (currentFormat.expandEmptyElements) { + out.write(">"); + } + else { + out.write(" />"); + } + } + else { + out.write(">"); + + // For a special case where the content is only CDATA + // or Text we don't want to indent after the start or + // before the end tag. + + if (nextNonText(content, start) < size) { + // Case Mixed Content - normal indentation + newline(out); + printContentRange(out, content, start, size, + level + 1, namespaces); + newline(out); + indent(out, level); + } + else { + // Case all CDATA or Text - no indentation + printTextRange(out, content, start, size); + } + out.write(""); + } + + // remove declared namespaces from stack + while (namespaces.size() > previouslyDeclaredNamespaces) { + namespaces.pop(); + } + + // Restore our format settings + currentFormat = previousFormat; + } + + /** + * This will handle printing of content within a given range. + * The range to print is specified in typical Java fashion; the + * starting index is inclusive, while the ending index is + * exclusive. + * + * @param content List of content to output + * @param start index of first content node (inclusive. + * @param end index of last content node (exclusive). + * @param out Writer to use. + * @param level int level of indentation. + * @param namespaces List stack of Namespaces in scope. + */ + private void printContentRange(Writer out, List content, + int start, int end, int level, + NamespaceStack namespaces) + throws IOException { + boolean firstNode; // Flag for 1st node in content + Object next; // Node we're about to print + int first, index; // Indexes into the list of content + + index = start; + while (index < end) { + firstNode = (index == start) ? true : false; + next = content.get(index); + + // + // Handle consecutive CDATA, Text, and EntityRef nodes all at once + // + if ((next instanceof Text) || (next instanceof EntityRef)) { + first = skipLeadingWhite(content, index); + // Set index to next node for loop + index = nextNonText(content, first); + + // If it's not all whitespace - print it! + if (first < index) { + if (!firstNode) + newline(out); + indent(out, level); + printTextRange(out, content, first, index); + } + continue; + } + + // + // Handle other nodes + // + if (!firstNode) { + newline(out); + } + + indent(out, level); + + if (next instanceof Comment) { + printComment(out, (Comment)next); + } + else if (next instanceof Element) { + printElement(out, (Element)next, level, namespaces); + } + else if (next instanceof ProcessingInstruction) { + printProcessingInstruction(out, (ProcessingInstruction)next); + } + else { + // XXX if we get here then we have a illegal content, for + // now we'll just ignore it (probably should throw + // a exception) + } + + index++; + } /* while */ + } + + /** + * This will handle printing of a sequence of {@link CDATA} + * or {@link Text} nodes. It is an error to have any other + * pass this method any other type of node. + * + * @param content List of content to output + * @param start index of first content node (inclusive). + * @param end index of last content node (exclusive). + * @param out Writer to use. + */ + private void printTextRange(Writer out, List content, int start, int end + ) throws IOException { + String previous; // Previous text printed + Object node; // Next node to print + String next; // Next text to print + + previous = null; + + // Remove leading whitespace-only nodes + start = skipLeadingWhite(content, start); + + int size = content.size(); + if (start < size) { + // And remove trialing whitespace-only nodes + end = skipTrailingWhite(content, end); + + for (int i = start; i < end; i++) { + node = content.get(i); + + // Get the unmangled version of the text + // we are about to print + if (node instanceof Text) { + next = ((Text) node).getText(); + } + else if (node instanceof EntityRef) { + next = "&" + ((EntityRef) node).getValue() + ";"; + } + else { + throw new IllegalStateException("Should see only " + + "CDATA, Text, or EntityRef"); + } + + // This may save a little time + if (next == null || "".equals(next)) { + continue; + } + + // Determine if we need to pad the output (padding is + // only need in trim or normalizing mode) + if (previous != null) { // Not 1st node + if (currentFormat.mode == Format.TextMode.NORMALIZE || + currentFormat.mode == Format.TextMode.TRIM) { + if ((endsWithWhite(previous)) || + (startsWithWhite(next))) { + out.write(" "); + } + } + } + + // Print the node + if (node instanceof CDATA) { + printCDATA(out, (CDATA) node); + } + else if (node instanceof EntityRef) { + printEntityRef(out, (EntityRef) node); + } + else { + printString(out, next); + } + + previous = next; + } + } + } + + /** + * This will handle printing of any needed {@link Namespace} + * declarations. + * + * @param ns Namespace to print definition of + * @param out Writer to use. + */ + private void printNamespace(Writer out, Namespace ns, + NamespaceStack namespaces) + throws IOException { + String prefix = ns.getPrefix(); + String uri = ns.getURI(); + + // Already printed namespace decl? + if (uri.equals(namespaces.getURI(prefix))) { + return; + } + + out.write(" xmlns"); + if (!prefix.equals("")) { + out.write(":"); + out.write(prefix); + } + out.write("=\""); + out.write(escapeAttributeEntities(uri)); + out.write("\""); + namespaces.push(ns); + } + + /** + * This will handle printing of a {@link Attribute} list. + * + * @param attributes List of Attribute objcts + * @param out Writer to use + */ + protected void printAttributes(Writer out, List attributes, Element parent, + NamespaceStack namespaces) + throws IOException { + + // I do not yet handle the case where the same prefix maps to + // two different URIs. For attributes on the same element + // this is illegal; but as yet we don't throw an exception + // if someone tries to do this + // Set prefixes = new HashSet(); + for (int i = 0; i < attributes.size(); i++) { + Attribute attribute = (Attribute) attributes.get(i); + Namespace ns = attribute.getNamespace(); + if ((ns != Namespace.NO_NAMESPACE) && + (ns != Namespace.XML_NAMESPACE)) { + printNamespace(out, ns, namespaces); + } + + out.write(" "); + printQualifiedName(out, attribute); + out.write("="); + + out.write("\""); + out.write(escapeAttributeEntities(attribute.getValue())); + out.write("\""); + } + } + + private void printElementNamespace(Writer out, Element element, + NamespaceStack namespaces) + throws IOException { + // Add namespace decl only if it's not the XML namespace and it's + // not the NO_NAMESPACE with the prefix "" not yet mapped + // (we do output xmlns="" if the "" prefix was already used and we + // need to reclaim it for the NO_NAMESPACE) + Namespace ns = element.getNamespace(); + if (ns == Namespace.XML_NAMESPACE) { + return; + } + if ( !((ns == Namespace.NO_NAMESPACE) && + (namespaces.getURI("") == null))) { + printNamespace(out, ns, namespaces); + } + } + + private void printAdditionalNamespaces(Writer out, Element element, + NamespaceStack namespaces) + throws IOException { + List list = element.getAdditionalNamespaces(); + if (list != null) { + for (int i = 0; i < list.size(); i++) { + Namespace additional = (Namespace)list.get(i); + printNamespace(out, additional, namespaces); + } + } + } + + // * * * * * * * * * * Support methods * * * * * * * * * * + // * * * * * * * * * * Support methods * * * * * * * * * * + + /** + * This will print a newline only if indent is not null. + * + * @param out Writer to use + */ + private void newline(Writer out) throws IOException { + if (currentFormat.indent != null) { + writeLineSeparator(out); + } + } + + /** + * This will print indents only if indent is not null or the empty string. + * + * @param out Writer to use + * @param level current indent level + */ + private void indent(Writer out, int level) throws IOException { + if (currentFormat.indent == null || + currentFormat.indent.equals("")) { + return; + } + + for (int i = 0; i < level; i++) { + out.write(currentFormat.indent); + } + } + + // Returns the index of the first non-all-whitespace CDATA or Text, + // index = content.size() is returned if content contains + // all whitespace. + // @param start index to begin search (inclusive) + private int skipLeadingWhite(List content, int start) { + if (start < 0) { + start = 0; + } + + int index = start; + int size = content.size(); + if (currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE + || currentFormat.mode == Format.TextMode.NORMALIZE + || currentFormat.mode == Format.TextMode.TRIM) { + while (index < size) { + if (!isAllWhitespace(content.get(index))) { + return index; + } + index++; + } + } + return index; + } + + // Return the index + 1 of the last non-all-whitespace CDATA or + // Text node, index < 0 is returned + // if content contains all whitespace. + // @param start index to begin search (exclusive) + private int skipTrailingWhite(List content, int start) { + int size = content.size(); + if (start > size) { + start = size; + } + + int index = start; + if (currentFormat.mode == Format.TextMode.TRIM_FULL_WHITE + || currentFormat.mode == Format.TextMode.NORMALIZE + || currentFormat.mode == Format.TextMode.TRIM) { + while (index >= 0) { + if (!isAllWhitespace(content.get(index - 1))) + break; + --index; + } + } + return index; + } + + // Return the next non-CDATA, non-Text, or non-EntityRef node, + // index = content.size() is returned if there is no more non-CDATA, + // non-Text, or non-EntiryRef nodes + // @param start index to begin search (inclusive) + private static int nextNonText(List content, int start) { + if (start < 0) { + start = 0; + } + + int index = start; + int size = content.size(); + while (index < size) { + Object node = content.get(index); + if (!((node instanceof Text) || (node instanceof EntityRef))) { + return index; + } + index++; + } + return size; + } + + // Determine if a Object is all whitespace + private boolean isAllWhitespace(Object obj) { + String str = null; + + if (obj instanceof String) { + str = (String) obj; + } + else if (obj instanceof Text) { + str = ((Text) obj).getText(); + } + else if (obj instanceof EntityRef) { + return false; + } + else { + return false; + } + + for (int i = 0; i < str.length(); i++) { + if (!Verifier.isXMLWhitespace(str.charAt(i))) + return false; + } + return true; + } + + // Determine if a string starts with a XML whitespace. + private boolean startsWithWhite(String str) { + if ((str != null) && + (str.length() > 0) && + Verifier.isXMLWhitespace(str.charAt(0))) { + return true; + } + return false; + } + + // Determine if a string ends with a XML whitespace. + private boolean endsWithWhite(String str) { + if ((str != null) && + (str.length() > 0) && + Verifier.isXMLWhitespace(str.charAt(str.length() - 1))) { + return true; + } + return false; + } + + /** + * This will take the pre-defined entities in XML 1.0 and + * convert their character representation to the appropriate + * entity reference, suitable for XML attributes. It does not convert + * the single quote (') because it's not necessary as the outputter + * writes attributes surrounded by double-quotes. + * + * @param str String input to escape. + * @return String with escaped content. + * @throws IllegalArgumentException if an entity can not be escaped + */ + public String escapeAttributeEntities(String str) { + StringBuffer buffer; + int ch, pos; + String entity; + EscapeStrategy strategy = currentFormat.escapeStrategy; + + buffer = null; + for (int i = 0; i < str.length(); i++) { + ch = str.charAt(i); + pos = i; + switch(ch) { + case '<' : + entity = "<"; + break; + case '>' : + entity = ">"; + break; +/* + case '\'' : + entity = "'"; + break; +*/ + case '\"' : + entity = """; + break; + case '&' : + entity = "&"; + break; + case '\r' : + entity = " "; + break; + case '\t' : + entity = " "; + break; + case '\n' : + entity = " "; + break; + default : + + if (strategy.shouldEscape((char) ch)) { + // Make sure what we are escaping is not the + // Beginning of a multi-byte character. + if (Verifier.isHighSurrogate((char) ch)) { + // This is a the high of a surrogate pair + i++; + if (i < str.length()) { + char low = str.charAt(i); + if(!Verifier.isLowSurrogate(low)) { + throw new IllegalDataException("Could not decode surrogate pair 0x" + + Integer.toHexString(ch) + " / 0x" + Integer.toHexString(low)); + } + ch = Verifier.decodeSurrogatePair((char) ch, low); + } else { + throw new IllegalDataException("Surrogate pair 0x" + + Integer.toHexString(ch) + " truncated"); + } + } + entity = "&#x" + Integer.toHexString(ch) + ";"; + } + else { + entity = null; + } + break; + } + if (buffer == null) { + if (entity != null) { + // An entity occurred, so we'll have to use StringBuffer + // (allocate room for it plus a few more entities). + buffer = new StringBuffer(str.length() + 20); + // Copy previous skipped characters and fall through + // to pickup current character + buffer.append(str.substring(0, pos)); + buffer.append(entity); + } + } + else { + if (entity == null) { + buffer.append((char) ch); + } + else { + buffer.append(entity); + } + } + } + + // If there were any entities, return the escaped characters + // that we put in the StringBuffer. Otherwise, just return + // the unmodified input string. + return (buffer == null) ? str : buffer.toString(); + } + + + /** + * This will take the three pre-defined entities in XML 1.0 + * (used specifically in XML elements) and convert their character + * representation to the appropriate entity reference, suitable for + * XML element content. + * + * @param str String input to escape. + * @return String with escaped content. + * @throws IllegalArgumentException if an entity can not be escaped + */ + public String escapeElementEntities(String str) { + if (escapeOutput == false) return str; + + StringBuffer buffer; + int ch, pos; + String entity; + EscapeStrategy strategy = currentFormat.escapeStrategy; + + buffer = null; + for (int i = 0; i < str.length(); i++) { + ch = str.charAt(i); + pos = i; + switch(ch) { + case '<' : + entity = "<"; + break; + case '>' : + entity = ">"; + break; + case '&' : + entity = "&"; + break; + case '\r' : + entity = " "; + break; + case '\n' : + entity = currentFormat.lineSeparator; + break; + default : + + if (strategy.shouldEscape((char) ch)) { + + //make sure what we are escaping is not the + //beginning of a multi-byte character. + if(Verifier.isHighSurrogate((char) ch)) { + //this is a the high of a surrogate pair + i++; + if (i < str.length()) { + char low = str.charAt(i); + if(!Verifier.isLowSurrogate(low)) { + throw new IllegalDataException("Could not decode surrogate pair 0x" + + Integer.toHexString(ch) + " / 0x" + Integer.toHexString(low)); + } + ch = Verifier.decodeSurrogatePair((char) ch, low); + } else { + throw new IllegalDataException("Surrogate pair 0x" + + Integer.toHexString(ch) + " truncated"); + } + } + entity = "&#x" + Integer.toHexString(ch) + ";"; + } + else { + entity = null; + } + break; + } + if (buffer == null) { + if (entity != null) { + // An entity occurred, so we'll have to use StringBuffer + // (allocate room for it plus a few more entities). + buffer = new StringBuffer(str.length() + 20); + // Copy previous skipped characters and fall through + // to pickup current character + buffer.append(str.substring(0, pos)); + buffer.append(entity); + } + } + else { + if (entity == null) { + buffer.append((char) ch); + } + else { + buffer.append(entity); + } + } + } + + // If there were any entities, return the escaped characters + // that we put in the StringBuffer. Otherwise, just return + // the unmodified input string. + return (buffer == null) ? str : buffer.toString(); + } + + /** + * Returns a copy of this XMLOutputter. + */ + public Object clone() { + // Implementation notes: Since all state of an XMLOutputter is + // embodied in simple private instance variables, Object.clone + // can be used. Note that since Object.clone is totally + // broken, we must catch an exception that will never be + // thrown. + try { + return super.clone(); + } + catch (java.lang.CloneNotSupportedException e) { + // even though this should never ever happen, it's still + // possible to fool Java into throwing a + // CloneNotSupportedException. If that happens, we + // shouldn't swallow it. + throw new RuntimeException(e.toString()); + } + } + + /** + * Return a string listing of the settings for this + * XMLOutputter instance. + * + * @return a string listing the settings for this XMLOutputter instance + */ + public String toString() { + StringBuffer buffer = new StringBuffer(); + if (userFormat.lineSeparator != null) { + for (int i = 0; i < userFormat.lineSeparator.length(); i++) { + char ch = userFormat.lineSeparator.charAt(i); + switch (ch) { + case '\r': buffer.append("\\r"); + break; + case '\n': buffer.append("\\n"); + break; + case '\t': buffer.append("\\t"); + break; + default: buffer.append("[" + ((int)ch) + "]"); + break; + } + } + } + else { + buffer.append("null"); + } + + return ( + "XMLOutputter[omitDeclaration = " + userFormat.omitDeclaration + ", " + + "encoding = " + userFormat.encoding + ", " + + "omitEncoding = " + userFormat.omitEncoding + ", " + + "indent = '" + userFormat.indent + "'" + ", " + + "expandEmptyElements = " + userFormat.expandEmptyElements + ", " + + "lineSeparator = '" + buffer.toString() + "', " + + "textMode = " + userFormat.mode + "]" + ); + } + + /** + * Factory for making new NamespaceStack objects. The NamespaceStack + * created is actually an inner class extending the package protected + * NamespaceStack, as a way to make NamespaceStack "friendly" toward + * subclassers. + */ + private NamespaceStack createNamespaceStack() { + // actually returns a XMLOutputter.NamespaceStack (see below) + return new NamespaceStack(); + } + + /** + * Our own null subclass of NamespaceStack. This plays a little + * trick with Java access protection. We want subclasses of + * XMLOutputter to be able to override protected methods that + * declare a NamespaceStack parameter, but we don't want to + * declare the parent NamespaceStack class as public. + */ + protected class NamespaceStack + extends org.jdom.output.NamespaceStack + { + } + + // Support method to print a name without using elt.getQualifiedName() + // and thus avoiding a StringBuffer creation and memory churn + private void printQualifiedName(Writer out, Element e) throws IOException { + if (e.getNamespace().getPrefix().length() == 0) { + out.write(e.getName()); + } + else { + out.write(e.getNamespace().getPrefix()); + out.write(':'); + out.write(e.getName()); + } + } + + // Support method to print a name without using att.getQualifiedName() + // and thus avoiding a StringBuffer creation and memory churn + private void printQualifiedName(Writer out, Attribute a) throws IOException { + String prefix = a.getNamespace().getPrefix(); + if ((prefix != null) && (!prefix.equals(""))) { + out.write(prefix); + out.write(':'); + out.write(a.getName()); + } + else { + out.write(a.getName()); + } + } + + // * * * * * * * * * * Deprecated methods * * * * * * * * * * + + /* The methods below here are deprecations of protected methods. We + * don't usually deprecate protected methods, so they're commented out. + * They're left here in case this mass deprecation causes people trouble. + * Since we're getting close to 1.0 it's actually better for people to + * raise issues early though. + */ + +} diff --git a/core/src/java/org/jdom/output/XMLOutputter2.java b/core/src/java/org/jdom/output/XMLOutputter2.java new file mode 100644 index 0000000..da7b831 --- /dev/null +++ b/core/src/java/org/jdom/output/XMLOutputter2.java @@ -0,0 +1,1096 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output; + +import java.io.BufferedOutputStream; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.support.AbstractXMLOutputProcessor; +import org.jdom.output.support.FormatStack; +import org.jdom.output.support.XMLOutputProcessor; + +/** + * Outputs a JDOM document as a stream of bytes. + *

+ * The XMLOutputter can manage many styles of document formatting, from + * untouched to 'pretty' printed. The default is to output the document content + * exactly as created, but this can be changed by setting a new Format object: + *

    + *
  • For pretty-print output, use + * {@link Format#getPrettyFormat()}. + *
  • For whitespace-normalised output, use + * {@link Format#getCompactFormat()}. + *
  • For unmodified-format output, use + * {@link Format#getRawFormat()}. + *
+ *

+ * There are {@link #output output(...)} methods to print any of + * the standard JDOM classes to either a Writer or an OutputStream. + *

+ * Warning: When outputting to a Writer, make sure the writer's encoding + * matches the encoding setting in the Format object. This ensures the encoding + * in which the content is written (controlled by the Writer configuration) + * matches the encoding placed in the document's XML declaration (controlled by + * the XMLOutputter). Because a Writer cannot be queried for its encoding, the + * information must be passed to the Format manually in its constructor or via + * the {@link Format#setEncoding} method. The default encoding is + * UTF-8. + *

+ * The methods {@link #outputString outputString(...)} are for + * convenience only; for top performance you should call one of the {@link + * #output output(...)} methods and pass in your own Writer or + * OutputStream if possible. + *

+ * All of the output*(...) methods will flush the + * destination Writer or OutputStream before returning, and none of them + * will close() the destination. + *

+ * XML declarations are always printed on their own line followed by a line + * separator (this doesn't change the semantics of the document). To omit + * printing of the declaration use + * {@link Format#setOmitDeclaration}. To omit printing of the + * encoding in the declaration use {@link Format#setOmitEncoding}. + * Unfortunately there is currently no way to know the original encoding of the + * document. + *

+ * Empty elements are by default printed as <empty/>, but this can be + * configured with {@link Format#setExpandEmptyElements} to cause + * them to be expanded to <empty></empty>. + *

+ * If changing the {@link Format} settings are insufficient for your output + * needs you can customise this XMLOutputter further by setting a different + * {@link XMLOutputProcessor} with the + * {@link #setXMLOutputProcessor(XMLOutputProcessor)} method or an appropriate + * constructor. A fully-enabled Abstract class + * {@link AbstractXMLOutputProcessor} is available to be further extended to + * your needs if all you want to do is tweak some details. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Jason Reid + * @author Wolfgang Werner + * @author Elliotte Rusty Harold + * @author David & Will (from Post Tool Design) + * @author Dan Schaffer + * @author Alex Chaffee + * @author Bradley S. Huffman + */ + +public final class XMLOutputter2 implements Cloneable { + + /* + * ===================================================================== + * Static content. + * ===================================================================== + */ + + /** + * Get an OutputStreamWriter, use specified encoding. + * + * @param out + * The OutputStream to wrap in the writer + * @param format + * The format is used to obtain the Character Encoding. + * @return An Writer (Buffered) that delegates to the specified output steam + * @throws java.io.UnsupportedEncodingException + */ + private static final Writer makeWriter(final OutputStream out, + final Format format) + throws java.io.UnsupportedEncodingException { + return new BufferedWriter(new OutputStreamWriter( + new BufferedOutputStream(out), format.getEncoding())); + } + + /** + * Create a final and static instance of the AbstractXMLOutputProcessor The + * final part is important because it improves performance. + *

+ * The JDOM user can change the actual XMLOutputProcessor with the + * {@link XMLOutputter2#setXMLOutputProcessor(XMLOutputProcessor)} method. + * + * @author rolf + */ + private static final class DefaultXMLProcessor + extends AbstractXMLOutputProcessor { + + /** + * A helper method to implement backward-compatibility with JDOM1 + * + * @see XMLOutputter2#escapeAttributeEntities(String) + * @param str + * The String to output. + * @param format + * The format details to use. + * @return The input String escaped as an attribute value. + */ + public String escapeAttributeEntities(String str, Format format) { + StringWriter sw = new StringWriter(); + try { + super.attributeEscapedEntitiesFilter(sw, new FormatStack(format), str); + } catch (IOException e) { + // no IOException on StringWriter.... + } + return sw.toString(); + } + + /** + * A helper method to implement backward-compatibility with JDOM1 + * + * @see XMLOutputter2#escapeElementEntities(String) + * @param str + * The String to output. + * @param format + * The format details to use. + * @return The input String escaped as an element text value. + */ + public final String escapeElementEntities(final String str, + final Format format) { + return Format.escapeText(format.getEscapeStrategy(), + format.getLineSeparator(), str); + } + + } + + /** + * This constant XMLOutputProcessor is used for all non-customised + * XMLOutputters + */ + private static final DefaultXMLProcessor DEFAULTPROCESSOR = + new DefaultXMLProcessor(); + + /* + * ===================================================================== + * Instance content. + * ===================================================================== + */ + + // For normal output + private Format myFormat = null; + + // The actual XMLOutputProcessor to delegate to. + private XMLOutputProcessor myProcessor = null; + + /* + * ===================================================================== + * Constructors + * ===================================================================== + */ + + /** + * This will create an XMLOutputter with the specified format + * characteristics. + *

+ * Note: the format object is cloned internally before use. If you + * want to modify the Format after constructing the XMLOutputter you can + * modify the Format instance {@link #getFormat()} returns. + * + * @param format + * The Format instance to use. This instance will be cloned() and as + * a consequence, changes made to the specified format instance + * will not be reflected in this XMLOutputter. A null input + * format indicates that XMLOutputter should use the default + * {@link Format#getRawFormat()} + * @param processor + * The XMLOutputProcessor to delegate output to. If null the + * XMLOutputter will use the default XMLOutputProcessor. + */ + public XMLOutputter2(Format format, XMLOutputProcessor processor) { + myFormat = format == null ? Format.getRawFormat() : format.clone(); + myProcessor = processor == null ? DEFAULTPROCESSOR : processor; + } + + /** + * This will create an XMLOutputter with a default + * {@link Format} and {@link XMLOutputProcessor}. + */ + public XMLOutputter2() { + this(null, null); + } + + /** + * This will create an XMLOutputter with the same + * customisations set in the given XMLOutputter instance. Note + * that XMLOutputter two = one.clone(); would work equally + * well. + * + * @param that + * the XMLOutputter to clone + */ + public XMLOutputter2(XMLOutputter2 that) { + this(that.myFormat, null); + } + + /** + * This will create an XMLOutputter with the specified format + * characteristics. + *

+ * Note: the format object is cloned internally before use. + * + * @param format + * The Format instance to use. This instance will be cloned() and as + * a consequence, changes made to the specified format instance + * will not be reflected in this XMLOutputter. A null input + * format indicates that XMLOutputter should use the default + * {@link Format#getRawFormat()} + */ + public XMLOutputter2(Format format) { + this(format, null); + } + + /** + * This will create an XMLOutputter with the specified + * XMLOutputProcessor. + * + * @param processor + * The XMLOutputProcessor to delegate output to. If null the + * XMLOutputter will use the default XMLOutputProcessor. + */ + public XMLOutputter2(XMLOutputProcessor processor) { + this(null, processor); + } + + /* + * ======================================================================= + * API - Settings... + * ======================================================================= + */ + + /** + * Sets the new format logic for the XMLOutputter. Note the Format object is + * cloned internally before use. + * + * @see #getFormat() + * @param newFormat + * the format to use for subsequent output + */ + public void setFormat(Format newFormat) { + this.myFormat = newFormat.clone(); + } + + /** + * Returns the current format in use by the XMLOutputter. Note the Format + * object returned is not a clone of the one used internally, thus, + * an XMLOutputter instance is able to have it's Format changed by changing + * the settings on the Format instance returned by this method. + * + * @return the current Format instance used by this XMLOutputter. + */ + public Format getFormat() { + return myFormat; + } + + /** + * Returns the current XMLOutputProcessor instance in use by the + * XMLOutputter. + * + * @return the current XMLOutputProcessor instance. + */ + public XMLOutputProcessor getXMLOutputProcessor() { + return myProcessor; + } + + /** + * Sets a new XMLOutputProcessor instance for this XMLOutputter. Note the + * processor object is expected to be thread-safe. + * + * @param processor + * the new XMLOutputProcesor to use for output + */ + public void setXMLOutputProcessor(XMLOutputProcessor processor) { + this.myProcessor = processor; + } + + /* + * ======================================================================= + * API - Output to STREAM Methods ... All methods defer to the WRITER + * equivalents + * ======================================================================= + */ + + /** + * This will print the {@link Document} to the given + * OutputStream. The characters are printed using the encoding specified in + * the constructor, or a default of UTF-8. + * + * @param doc + * Document to format. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Document doc, OutputStream out) + throws IOException { + output(doc, makeWriter(out, myFormat)); + } + + /** + * This will print the {@link DocType} to the given + * OutputStream. + * + * @param doctype + * DocType to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(DocType doctype, OutputStream out) throws IOException { + output(doctype, makeWriter(out, myFormat)); + } + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param element + * Element to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Element element, OutputStream out) throws IOException { + output(element, makeWriter(out, myFormat)); + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and attributes. + * This can be useful for printing the content of an element that contains + * HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param element + * Element to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void outputElementContent(Element element, OutputStream out) + throws IOException { + outputElementContent(element, makeWriter(out, myFormat)); + } + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + *

+ * The list is assumed to contain legal JDOM nodes. If other content is + * coerced on to the list it will cause ClassCastExceptions, and null Lists + * or null list members will cause NullPointerException. + * + * @param list + * List of nodes. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws ClassCastException + * if non-{@link Content} is forced in to the list + * @throws NullPointerException + * if the List is null or contains null members. + */ + public final void output(List list, OutputStream out) + throws IOException { + output(list, makeWriter(out, myFormat)); // output() flushes + } + + /** + * Print out a {@link CDATA} node. + * + * @param cdata + * CDATA to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(CDATA cdata, OutputStream out) throws IOException { + output(cdata, makeWriter(out, myFormat)); // output() flushes + } + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param text + * Text to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Text text, OutputStream out) throws IOException { + output(text, makeWriter(out, myFormat)); // output() flushes + } + + /** + * Print out a {@link Comment}. + * + * @param comment + * Comment to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Comment comment, OutputStream out) throws IOException { + output(comment, makeWriter(out, myFormat)); // output() flushes + } + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param pi + * ProcessingInstruction to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(ProcessingInstruction pi, OutputStream out) + throws IOException { + output(pi, makeWriter(out, myFormat)); // output() flushes + } + + /** + * Print out a {@link EntityRef}. + * + * @param entity + * EntityRef to output. + * @param out + * OutputStream to use. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public void output(EntityRef entity, OutputStream out) throws IOException { + output(entity, makeWriter(out, myFormat)); // output() flushes + } + + /* + * ======================================================================= + * API - Output to STRING Methods ... All methods defer to the WRITER + * equivalents + * ======================================================================= + */ + + /** + * Return a string representing a {@link Document}. Uses an internal + * StringWriter. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param doc + * Document to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(Document doc) { + StringWriter out = new StringWriter(); + try { + output(doc, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing a {@link DocType}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param doctype + * DocType to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(DocType doctype) { + StringWriter out = new StringWriter(); + try { + output(doctype, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing an {@link Element}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param element + * Element to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(Element element) { + StringWriter out = new StringWriter(); + try { + output(element, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing a List of {@link Content} nodes.
+ * The list is assumed to contain legal JDOM nodes. If other content is + * coerced on to the list it will cause ClassCastExceptions, and null List + * members will cause NullPointerException. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param list + * List to format. + * @return the input content formatted as an XML String. + * @throws ClassCastException + * if non-{@link Content} is forced in to the list + * @throws NullPointerException + * if the List is null or contains null members. + */ + public final String outputString(List list) { + StringWriter out = new StringWriter(); + try { + output(list, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing a {@link CDATA} node. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param cdata + * CDATA to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(CDATA cdata) { + StringWriter out = new StringWriter(); + try { + output(cdata, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing a {@link Text} node. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param text + * Text to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(Text text) { + StringWriter out = new StringWriter(); + try { + output(text, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing a {@link Comment}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param comment + * Comment to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(Comment comment) { + StringWriter out = new StringWriter(); + try { + output(comment, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing a {@link ProcessingInstruction}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param pi + * ProcessingInstruction to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(ProcessingInstruction pi) { + StringWriter out = new StringWriter(); + try { + output(pi, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * Return a string representing an {@link EntityRef}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param entity + * EntityRef to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputString(EntityRef entity) { + StringWriter out = new StringWriter(); + try { + output(entity, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and attributes. + * This can be useful for printing the content of an element that contains + * HTML, like "<description>JDOM is + * <b>fun>!</description>". + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param element + * Element to output. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public final String outputElementContentString(Element element) { + StringWriter out = new StringWriter(); + try { + outputElementContent(element, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + /* + * ======================================================================== + * API - Output to WRITER Methods ... These are the core methods that the + * Stream and String output methods call. On the other hand, these methods + * defer to the protected/override methods. These methods flush the writer. + * ======================================================================== + */ + + /** + * This will print the Document to the given Writer. + *

+ * Warning: using your own Writer may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param doc + * Document to format. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Document doc, Writer out) throws IOException { + myProcessor.process(out, myFormat, doc); + out.flush(); + } + + /** + * Print out the {@link DocType}. + * + * @param doctype + * DocType to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(DocType doctype, Writer out) throws IOException { + myProcessor.process(out, myFormat, doctype); + out.flush(); + } + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param element + * Element to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Element element, Writer out) throws IOException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + myProcessor.process(out, myFormat, element); + out.flush(); + } + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and attributes. + * This can be useful for printing the content of an element that contains + * HTML, like "<description>JDOM is + * <b>fun>!</description>". + * + * @param element + * Element to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void outputElementContent(Element element, Writer out) + throws IOException { + myProcessor.process(out, myFormat, element.getContent()); + out.flush(); + } + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param list + * List of nodes. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(List list, Writer out) + throws IOException { + myProcessor.process(out, myFormat, list); + out.flush(); + } + + /** + * Print out a {@link CDATA} node. + * + * @param cdata + * CDATA to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(CDATA cdata, Writer out) throws IOException { + myProcessor.process(out, myFormat, cdata); + out.flush(); + } + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param text + * Text to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Text text, Writer out) throws IOException { + myProcessor.process(out, myFormat, text); + out.flush(); + } + + /** + * Print out a {@link Comment}. + * + * @param comment + * Comment to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(Comment comment, Writer out) throws IOException { + myProcessor.process(out, myFormat, comment); + out.flush(); + } + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param pi + * ProcessingInstruction to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(ProcessingInstruction pi, Writer out) + throws IOException { + myProcessor.process(out, myFormat, pi); + out.flush(); + } + + /** + * Print out an {@link EntityRef}. + * + * @param entity + * EntityRef to output. + * @param out + * Writer to use. + * @throws IOException + * - if there's any problem writing. + * @throws NullPointerException + * if the specified content is null. + */ + public final void output(EntityRef entity, Writer out) throws IOException { + myProcessor.process(out, myFormat, entity); + out.flush(); + } + + /* + * ======================================================================== + * SpecialCaseMethods for maintaining API Compatibility + * ======================================================================== + */ + + /** + * Escape any characters in the input string in such a way that the returned + * value is valid as output in an XML Attribute value. + * + * @param str + * the input String to escape + * @return the escaped version of the input String + */ + public String escapeAttributeEntities(String str) { + return DEFAULTPROCESSOR.escapeAttributeEntities(str, myFormat); + } + + /** + * Escape any characters in the input string in such a way that the returned + * value is valid as output in an XML Element text. + * + * @param str + * the input String to escape + * @return the escaped version of the input String + */ + public String escapeElementEntities(String str) { + return DEFAULTPROCESSOR.escapeElementEntities(str, myFormat); + } + + /* + * ======================================================================== + * Basic Support methods. + * ======================================================================== + */ + + /** + * Returns a cloned copy of this XMLOutputter. + */ + @Override + public XMLOutputter2 clone() { + // Implementation notes: Since all state of an XMLOutputter is + // embodied in simple private instance variables, Object.clone + // can be used. Note that since Object.clone is totally + // broken, we must catch an exception that will never be + // thrown. + try { + return (XMLOutputter2) super.clone(); + } catch (java.lang.CloneNotSupportedException e) { + // even though this should never ever happen, it's still + // possible to fool Java into throwing a + // CloneNotSupportedException. If that happens, we + // shouldn't swallow it. + throw new RuntimeException(e.toString()); + } + } + + /** + * Return a string listing of the settings for this XMLOutputter instance. + * + * @return a string listing the settings for this XMLOutputter instance + */ + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer.append("XMLOutputter[omitDeclaration = "); + buffer.append(myFormat.omitDeclaration); + buffer.append(", "); + buffer.append("encoding = "); + buffer.append(myFormat.encoding); + buffer.append(", "); + buffer.append("omitEncoding = "); + buffer.append(myFormat.omitEncoding); + buffer.append(", "); + buffer.append("indent = '"); + buffer.append(myFormat.indent); + buffer.append("'"); + buffer.append(", "); + buffer.append("expandEmptyElements = "); + buffer.append(myFormat.expandEmptyElements); + buffer.append(", "); + buffer.append("lineSeparator = '"); + for (char ch : myFormat.lineSeparator.toCharArray()) { + switch (ch) { + case '\r': + buffer.append("\\r"); + break; + case '\n': + buffer.append("\\n"); + break; + case '\t': + buffer.append("\\t"); + break; + default: + buffer.append("[" + ((int) ch) + "]"); + break; + } + } + buffer.append("', "); + buffer.append("textMode = "); + buffer.append(myFormat.mode + "]"); + return buffer.toString(); + } + +} diff --git a/core/src/java/org/jdom/output/package.html b/core/src/java/org/jdom/output/package.html new file mode 100644 index 0000000..a9255aa --- /dev/null +++ b/core/src/java/org/jdom/output/package.html @@ -0,0 +1,18 @@ + + +Classes to output JDOM documents to various destinations. The most common +outputter class is XMLOutputter which outputs a document (or part of a +document) as a stream of bytes. Format and EscapeStrategy support the +XMLOutputter in letting you choose how the output should be formatted and how +special characters should be escaped. + +SAXOutputter lets you output as a stream of SAX events (handy especially in +transformations). JDOMLocator supports SAXOutputter and helps you observe the +SAX output process. + +DOMOutputter lets you output a JDOM document as a DOM tree. + +StAXStreamOutputter lets you output the JDOM content to an XMLStreamWriter, and +the StAXEventOutputter lets you output the JDOM content to an XMLEventWriter. + + diff --git a/core/src/java/org/jdom/output/support/AbstractDOMOutputProcessor.java b/core/src/java/org/jdom/output/support/AbstractDOMOutputProcessor.java new file mode 100644 index 0000000..dd8d999 --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractDOMOutputProcessor.java @@ -0,0 +1,595 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Content.CType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMConstants; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.DOMOutputter; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.util.NamespaceStack; + +/** + * This class provides a concrete implementation of {@link DOMOutputProcessor} + * for supporting the {@link DOMOutputter}. + *

+ *

Overview

+ *

+ * This class is marked abstract even though all methods are fully implemented. + * The process*(...) methods are public because they match the + * DOMOutputProcessor interface but the remaining methods are all protected. + *

+ * People who want to create a custom DOMOutputProcessor for DOMOutputter + * are able to extend this class and modify any functionality they want. Before + * sub-classing this you should first check to see if the {@link Format} class + * can get you the results you want. + *

+ * Subclasses of this should have reentrant methods. This is + * easiest to accomplish simply by not allowing any instance fields. If your + * sub-class has an instance field/variable, then it's probably broken. + *

+ *

The Stacks

+ *

+ * One significant feature of this implementation is that it creates and + * maintains both a {@link NamespaceStack} and {@link FormatStack} that are + * managed in the + * {@link #printElement(FormatStack, NamespaceStack, org.w3c.dom.Document, Element)} + * method. The stacks are pushed and popped in that method only. They + * significantly improve the performance and readability of the code. + *

+ * The NamespaceStack is only sent through to the + * {@link #printElement(FormatStack, NamespaceStack, org.w3c.dom.Document, Element)} + * and + * {@link #printContent(FormatStack, NamespaceStack, org.w3c.dom.Document, org.w3c.dom.Node, Walker)} + * methods, but the FormatStack is pushed through to all print* Methods. + *

+ *

Content Processing

+ *

+ * This class delegates the formatting of the content to the Walker classes + * and you can create your own custom walker by overriding the + * {@link #buildWalker(FormatStack, List, boolean)} method. + * + * @see DOMOutputter + * @see DOMOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public abstract class AbstractDOMOutputProcessor extends + AbstractOutputProcessor implements DOMOutputProcessor { + + /** + * This will handle adding any {@link Namespace} attributes to + * the DOM tree. + * + * @param ns + * Namespace to add definition of + */ + private static String getXmlnsTagFor(Namespace ns) { + String attrName = "xmlns"; + if (!ns.getPrefix().equals("")) { + attrName += ":"; + attrName += ns.getPrefix(); + } + return attrName; + } + + /* ******************************************* + * DOMOutputProcessor implementation. + * ******************************************* + */ + + @Override + public org.w3c.dom.Document process(org.w3c.dom.Document basedoc, + Format format, Document doc) { + return printDocument(new FormatStack(format), new NamespaceStack(), + basedoc, doc); + } + + @Override + public org.w3c.dom.Element process(org.w3c.dom.Document basedoc, + Format format, Element element) { + return printElement(new FormatStack(format), new NamespaceStack(), + basedoc, element); + } + + @Override + public List process(org.w3c.dom.Document basedoc, + Format format, List list) { + List ret = new ArrayList( + list.size()); + FormatStack fstack = new FormatStack(format); + NamespaceStack nstack = new NamespaceStack(); + for (Content c : list) { + fstack.push(); + try { + org.w3c.dom.Node node = helperContentDispatcher(fstack, nstack, + basedoc, c); + if (node != null) { + ret.add(node); + } + } finally { + fstack.pop(); + } + } + return ret; + } + + @Override + public org.w3c.dom.CDATASection process(org.w3c.dom.Document basedoc, + Format format, CDATA cdata) { + final List list = Collections.singletonList(cdata); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + return printCDATA(fstack, basedoc, new CDATA(walker.text())); + } + if (c.getCType() == CType.CDATA) { + return printCDATA(fstack, basedoc, (CDATA)c); + } + } + // return an empty string if nothing happened. + return null; + } + + @Override + public org.w3c.dom.Text process(org.w3c.dom.Document basedoc, + Format format, Text text) { + final List list = Collections.singletonList(text); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + return printText(fstack, basedoc, new Text(walker.text())); + } + if (c.getCType() == CType.Text) { + return printText(fstack, basedoc, (Text)c); + } + } + // return an empty string if nothing happened. + return null; + } + + @Override + public org.w3c.dom.Comment process(org.w3c.dom.Document basedoc, + Format format, Comment comment) { + return printComment(new FormatStack(format), basedoc, comment); + } + + @Override + public org.w3c.dom.ProcessingInstruction process( + org.w3c.dom.Document basedoc, Format format, + ProcessingInstruction pi) { + return printProcessingInstruction(new FormatStack(format), basedoc, pi); + } + + @Override + public org.w3c.dom.EntityReference process(org.w3c.dom.Document basedoc, + Format format, EntityRef entity) { + return printEntityRef(new FormatStack(format), basedoc, entity); + } + + @Override + public org.w3c.dom.Attr process(org.w3c.dom.Document basedoc, Format format, + Attribute attribute) { + return printAttribute(new FormatStack(format), basedoc, attribute); + } + + /* ******************************************* + * Support methods for output. Should all be protected. All content-type + * print methods have a FormatStack. Only printContent is responsible for + * outputting appropriate indenting and newlines, which are easily available + * using the FormatStack.getLevelIndent() and FormatStack.getLevelEOL(). + * ******************************************* + */ + + /** + * This will handle printing of a {@link Document}. + * + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param doc + * Document to write. + * @return The input JDOM document converted to a DOM document. + */ + protected org.w3c.dom.Document printDocument(final FormatStack fstack, + final NamespaceStack nstack, final org.w3c.dom.Document basedoc, + final Document doc) { + + if (!fstack.isOmitDeclaration()) { + basedoc.setXmlVersion("1.0"); + } + + final int sz = doc.getContentSize(); + + if (sz > 0) { + for (int i = 0; i < sz; i++) { + final Content c = doc.getContent(i); + org.w3c.dom.Node n = null; + switch (c.getCType()) { + case Comment : + n = printComment(fstack, basedoc, (Comment)c); + break; + case DocType : + // cannot simply add a DocType to a DOM object + // it is added when the DOM Document is created. + // leave n as null + break; + case Element : + n = printElement(fstack, nstack, basedoc, (Element)c); + break; + case ProcessingInstruction : + n = printProcessingInstruction(fstack, basedoc, + (ProcessingInstruction)c); + break; + default : + // do nothing. + } + if (n != null) { + basedoc.appendChild(n); + } + } + } + + return basedoc; + } + + /** + * This will handle printing of a {@link ProcessingInstruction}. + * + * @param fstack + * the FormatStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param pi + * ProcessingInstruction to write. + * @return The input JDOM ProcessingInstruction converted to a DOM + * ProcessingInstruction. + */ + protected org.w3c.dom.ProcessingInstruction printProcessingInstruction( + final FormatStack fstack, final org.w3c.dom.Document basedoc, + final ProcessingInstruction pi) { + String target = pi.getTarget(); + String rawData = pi.getData(); + if (rawData == null || rawData.trim().length() == 0) { + rawData = ""; + } + return basedoc.createProcessingInstruction(target, rawData); + } + + /** + * This will handle printing of a {@link Comment}. + * + * @param fstack + * the FormatStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param comment + * Comment to write. + * @return The input JDOM Comment converted to a DOM Comment + */ + protected org.w3c.dom.Comment printComment(final FormatStack fstack, + final org.w3c.dom.Document basedoc, final Comment comment) { + return basedoc.createComment(comment.getText()); + } + + /** + * This will handle printing of an {@link EntityRef}. + * + * @param fstack + * the FormatStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param entity + * EntotyRef to write. + * @return The input JDOM EntityRef converted to a DOM EntityReference + */ + protected org.w3c.dom.EntityReference printEntityRef( + final FormatStack fstack, final org.w3c.dom.Document basedoc, + final EntityRef entity) { + return basedoc.createEntityReference(entity.getName()); + } + + /** + * This will handle printing of a {@link CDATA}. + * + * @param fstack + * the FormatStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param cdata + * CDATA to write. + * @return The input JDOM CDATA converted to a DOM CDATASection + */ + protected org.w3c.dom.CDATASection printCDATA(final FormatStack fstack, + final org.w3c.dom.Document basedoc, final CDATA cdata) { + // CDATAs are treated like text, not indented/newline content. + return basedoc.createCDATASection(cdata.getText()); + } + + /** + * This will handle printing of a {@link Text}. + * + * @param fstack + * the FormatStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param text + * Text to write. + * @return The input JDOM Text converted to a DOM Text + */ + protected org.w3c.dom.Text printText(final FormatStack fstack, + final org.w3c.dom.Document basedoc, final Text text) { + return basedoc.createTextNode(text.getText()); + } + + /** + * This will handle printing of a {@link Attribute}. + * + * @param fstack + * the FormatStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param attribute + * Attribute to write. + * @return The input JDOM Attribute converted to a DOM Attr + */ + protected org.w3c.dom.Attr printAttribute(final FormatStack fstack, + final org.w3c.dom.Document basedoc, final Attribute attribute) { + if (!attribute.isSpecified() && fstack.isSpecifiedAttributesOnly()) { + return null; + } + org.w3c.dom.Attr attr = basedoc.createAttributeNS( + attribute.getNamespaceURI(), attribute.getQualifiedName()); + attr.setValue(attribute.getValue()); + return attr; + } + + /** + * This will handle printing of an {@link Element}. + *

+ * This method arranges for outputting the Element infrastructure including + * Namespace Declarations and Attributes. + *

+ * The actual formatting of the content is managed by the Walker created for + * the Element's content. + *

+ * + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param element + * Element to write. + * @return The input JDOM Element converted to a DOM Element + */ + protected org.w3c.dom.Element printElement(final FormatStack fstack, + final NamespaceStack nstack, final org.w3c.dom.Document basedoc, + final Element element) { + + nstack.push(element); + try { + + TextMode textmode = fstack.getTextMode(); + + // Check for xml:space and adjust format settings + final String space = element.getAttributeValue("space", + Namespace.XML_NAMESPACE); + + if ("default".equals(space)) { + textmode = fstack.getDefaultMode(); + } else if ("preserve".equals(space)) { + textmode = TextMode.PRESERVE; + } + + org.w3c.dom.Element ret = basedoc.createElementNS( + element.getNamespaceURI(), element.getQualifiedName()); + + for (Namespace ns : nstack.addedForward()) { + if (ns == Namespace.XML_NAMESPACE) { + continue; + } + ret.setAttributeNS(JDOMConstants.NS_URI_XMLNS, getXmlnsTagFor(ns), ns.getURI()); + } + + if (element.hasAttributes()) { + for (Attribute att : element.getAttributes()) { + final org.w3c.dom.Attr a = printAttribute(fstack, basedoc, att); + if (a != null) { + ret.setAttributeNodeNS(a); + } + } + } + + final List content = element.getContent(); + + if (!content.isEmpty()) { + fstack.push(); + try { + fstack.setTextMode(textmode); + Walker walker = buildWalker(fstack, content, false); + + if (!walker.isAllText() && fstack.getPadBetween() != null) { + // we need to newline/indent + final org.w3c.dom.Text n = basedoc.createTextNode( + fstack.getPadBetween()); + ret.appendChild(n); + } + + printContent(fstack, nstack, basedoc, ret, walker); + + if (!walker.isAllText() && fstack.getPadLast() != null) { + // we need to newline/indent + final org.w3c.dom.Text n = basedoc.createTextNode( + fstack.getPadLast()); + ret.appendChild(n); + } + + } finally { + fstack.pop(); + } + } + + return ret; + + } finally { + nstack.pop(); + } + } + + /** + * This will handle printing of a List of {@link Content}. Uses the Walker + * to ensure formatting. + * + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param target + * the DOM node this content should be appended to. + * @param walker + * List of Content to write. + */ + protected void printContent(final FormatStack fstack, + final NamespaceStack nstack, final org.w3c.dom.Document basedoc, + final org.w3c.dom.Node target, final Walker walker) { + + while (walker.hasNext()) { + final Content c = walker.next(); + org.w3c.dom.Node n = null; + if (c == null) { + // Formatted Text or CDATA + final String text = walker.text(); + if (walker.isCDATA()) { + n = printCDATA(fstack, basedoc, new CDATA(text)); + } else { + n = printText(fstack, basedoc, new Text(text)); + } + } else { + n = helperContentDispatcher(fstack, nstack, + basedoc, c); + } + if (n != null) { + target.appendChild(n); + } + } + + } + + /** + * This method contains code which is reused in a number of places. It + * simply determines what content is passed in, and dispatches it to the + * correct print* method. + * + * @param fstack + * The current FormatStack + * @param nstack + * the NamespaceStack + * @param basedoc + * The org.w3c.dom.Document for creating DOM Nodes + * @param content + * The content to dispatch + * @return the input JDOM Content converted to a DOM Node. + */ + protected org.w3c.dom.Node helperContentDispatcher( + final FormatStack fstack, final NamespaceStack nstack, + final org.w3c.dom.Document basedoc, final Content content) { + switch (content.getCType()) { + case CDATA: + return printCDATA(fstack, basedoc, (CDATA) content); + case Comment: + return printComment(fstack, basedoc, (Comment) content); + case Element: + return printElement(fstack, nstack, basedoc, (Element) content); + case EntityRef: + return printEntityRef(fstack, basedoc, (EntityRef) content); + case ProcessingInstruction: + return printProcessingInstruction(fstack, basedoc, + (ProcessingInstruction) content); + case Text: + return printText(fstack, basedoc, (Text) content); + case DocType: + return null; + default: + throw new IllegalStateException("Unexpected Content " + + content.getCType()); + } + } + +} diff --git a/core/src/java/org/jdom/output/support/AbstractFormattedWalker.java b/core/src/java/org/jdom/output/support/AbstractFormattedWalker.java new file mode 100644 index 0000000..ce3c267 --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractFormattedWalker.java @@ -0,0 +1,603 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in mtsource and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of mtsource code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.jdom.CDATA; +import org.jdom.Content; +import org.jdom.internal.ArrayCopy; +import org.jdom.output.EscapeStrategy; +import org.jdom.output.Format; + +/** + * This Walker implementation walks a list of Content in a Formatted form of + * some sort. + *

+ * The JDOM content can be loosely categorised in to 'Text-like' content + * (consisting of Text, CDATA, and EntityRef), and everything else. This + * distinction is significant for for this class and it's sub-classes. + *

+ * There will be text manipulation, and some (but not necessarily + * all) Text-like content will be returned as text() instead of next(). + *

+ * The trick in this class is that it deals with the regular content, and + * delegates the Text-like content to the sub-classes. + *

+ * Subclasses are tasked with analysing chunks of Text-like content in the + * {@link #analyzeMultiText(MultiText, int, int)} method. The subclasses are + * responsible for adding the relevant text content to the suppliedMultiText + * instance in such a way as to result in the correct format. + *

+ * The Subclass needs to concern itself with only the text portion because this + * abstract class will ensure the Text-like content is appropriately indented. + * + * @author Rolf Lear + */ +public abstract class AbstractFormattedWalker implements Walker { + + /* + * We use Text instances to return formatted text to the caller. + * We do not need to validate the Text content... it is 'safe' to + * not use the default Text class. + */ + private static final CDATA CDATATOKEN = new CDATA(""); + + /** + * Indicate how text content should be added + * @author Rolf Lear + * + */ + protected enum Trim { + /** Left Trim */ + LEFT, + /** Right Trim */ + RIGHT, + /** Both Trim */ + BOTH, + /** Trim Both and replace all internal whitespace with a single space*/ + COMPACT, + /** No Trimming at all */ + NONE + } + + private static final Iterator EMPTYIT = new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public Content next() { + throw new NoSuchElementException("Cannot call next() on an empty iterator."); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot remove from an empty iterator."); + } + }; + + /** + * Collect together the items that constitute formatted Text-like content. + * + * @author Rolf Lear + * + */ + protected final class MultiText { + + + /** + * This is private so only this abstract class can create instances. + */ + private MultiText() { + } + + /** + * Ensure we have space for at least one more text-like item. + */ + private void ensurespace() { + if (mtsize >= mtdata.length) { + mtdata = ArrayCopy.copyOf(mtdata, mtsize + 1 + (mtsize / 2)); + mttext = ArrayCopy.copyOf(mttext, mtdata.length); + } + } + + /** + * Handle the case where we have been accumulating true text content, + * and the next item is not more text. + * @param postspace true if the last char in the text should be a space + */ + private void closeText() { + if (mtbuffer.length() == 0) { + // empty text does not need adding at all. + return; + } + ensurespace(); + mtdata[mtsize] = null; + mttext[mtsize++] = mtbuffer.toString(); + mtbuffer.setLength(0); + } + + /** + * Append some text to the text-like sequence that will be treated as + * plain XML text (PCDATA). If the last content added to this text-like + * sequence then this new text will be appended directly to the previous + * text. + * + * @param trim How to prepare the Text content + * @param text The actual Text content. + */ + public void appendText(final Trim trim, final String text) { + final int tlen = text.length(); + if (tlen == 0) { + return; + } + String toadd = null; + switch (trim) { + case NONE: + toadd = text; + break; + case BOTH: + toadd = Format.trimBoth(text); + break; + case LEFT: + toadd = Format.trimLeft(text); + break; + case RIGHT: + toadd = Format.trimRight(text); + break; + case COMPACT: + toadd = Format.compact(text); + break; + } + if (toadd != null) { + toadd = escapeText(toadd); + mtbuffer.append(toadd); + mtgottext = true; + } + } + + private String escapeText(final String text) { + if (escape == null || !fstack.getEscapeOutput()) { + return text; + } + return Format.escapeText(escape, endofline, text); + } + + private String escapeCDATA(final String text) { + if (escape == null) { + return text; + } + return text; + } + /** + * Append some text to the text-like sequence that will be treated as + * CDATA. + * @param trim How to prepare the CDATA content + * @param text The actual CDATA content. + */ + public void appendCDATA(final Trim trim, final String text) { + // this resets the mtbuffer too. + closeText(); + String toadd = null; + switch (trim) { + case NONE: + toadd = text; + break; + case BOTH: + toadd = Format.trimBoth(text); + break; + case LEFT: + toadd = Format.trimLeft(text); + break; + case RIGHT: + toadd = Format.trimRight(text); + break; + case COMPACT: + toadd = Format.compact(text); + break; + } + + toadd = escapeCDATA(toadd); + ensurespace(); + // mark this as being CDATA text + mtdata[mtsize] = CDATATOKEN; + mttext[mtsize++] = toadd; + + mtgottext = true; + + } + + /** + * Simple method that ensures the text is processed, regardless of + * content, and is never escaped. + * @param text + */ + private void forceAppend(final String text) { + mtgottext = true; + mtbuffer.append(text); + } + + /** + * Add some JDOM Content (typically an EntityRef) that will be treated + * as part of the Text-like sequence. + * @param c the content to add. + */ + public void appendRaw(final Content c) { + closeText(); + ensurespace(); + mttext[mtsize] = null; + mtdata[mtsize++] = c; + mtbuffer.setLength(0); + + } + + /** + * Indicate that there is no further content to be added to the + * text-like sequence. + */ + public void done() { + if (mtpostpad && newlineindent != null) { + // this will be ignored if there was not some content. + mtbuffer.append(newlineindent); + } + if (mtgottext) { + closeText(); + } + mtbuffer.setLength(0); + } + + } + + + + + private Content pending = null; + private final Iterator content; + private final boolean alltext; + private final boolean allwhite; + private final String newlineindent; + private final String endofline; + private final EscapeStrategy escape; + private final FormatStack fstack; + private boolean hasnext = true; + + + // MultiText handling changed in 2.0.5 + // MultiText is something quite complicated, but it goes something like this: + // XML Content is either text-like, or its not. If we encounter text-like content + // then we find out how many text-like contents are in a row, and we add them to a + // multi-text. We then either get to the end of the content, or a non-text content. + // If we complete the multitext, we then move on to the non-text item, and we set multitext + // to null. Both multitect and pendingmt are thus null. + // If the content following the non-text is then text-like, we populate pendingmt. + // bottom line is that multitext and pendingmt can never both be set. + // we use one set of variables to back up both of them. This is fast, and safe in a single + // threaded environment (which the Walkers are guaranteed to be in). + // all MultiText-specific variables have the names mt* + private MultiText multitext = null; + private MultiText pendingmt = null; + private final MultiText holdingmt = new MultiText(); + + private final StringBuilder mtbuffer = new StringBuilder(); + // if there should be indenting after this text. + private boolean mtpostpad; + // indicate whether there is something actually added. + private boolean mtgottext = false; + // the number of mixed content values. + private int mtsize = 0; + private int mtsourcesize = 0; + private Content[] mtsource = new Content[8]; + // the location of the processed content. + private Content[] mtdata = new Content[8]; + // whether the mixed content should be returned as raw JDOM objects + private String[] mttext = new String[8]; + + // the current cursor in the mixed content. + private int mtpos = -1; + // we cheat here by using Boolean as a three-state option... + // we expect it to be null often. + private Boolean mtwasescape; + + /** + * Create a Walker that preserves all content in its raw state. + * @param xx the content to walk. + * @param fstack the current FormatStack + * @param doescape Whether Text values should be escaped. + */ + public AbstractFormattedWalker(final List xx, + final FormatStack fstack, final boolean doescape) { + super(); + this.fstack = fstack; + this.content = xx.isEmpty() ? EMPTYIT : xx.iterator(); + this.escape = doescape ? fstack.getEscapeStrategy() : null; + newlineindent = fstack.getPadBetween(); + endofline = fstack.getLevelEOL(); + if (!content.hasNext()) { + alltext = true; + allwhite = true; + } else { + boolean atext = false; + boolean awhite = false; + pending = content.next(); + if (isTextLike(pending)) { + // the first item in the list is Text-like, and we pre-check + // to see whether all content is text.... and whether it amounts + // to something. + pendingmt = buildMultiText(true); + analyzeMultiText(pendingmt, 0, mtsourcesize); + pendingmt.done(); + + if (pending == null) { + atext = true; + awhite = mtsize == 0; + } + if (mtsize == 0) { + // first content in list is ignorable. + pendingmt = null; + } + } + alltext = atext; + allwhite = awhite; + } + hasnext = pendingmt != null || pending != null; + } + + @Override + public final Content next() { + + if (!hasnext) { + throw new NoSuchElementException("Cannot walk off end of Content"); + } + + if (multitext != null && mtpos + 1 >= mtsize) { + // finished this multitext. need to move on. + multitext = null; + resetMultiText(); + } + if (pendingmt != null) { + // we have a multi-text pending from the last block + // this will only be the case when the previous value was non-text. + if (mtwasescape != null && + fstack.getEscapeOutput() != mtwasescape.booleanValue()) { + // we calculated pending with one escape strategy, but it changed... + // we need to recalculate it.... + + mtsize = 0; + mtwasescape = fstack.getEscapeOutput(); + analyzeMultiText(pendingmt, 0, mtsourcesize); + pendingmt.done(); + } + multitext = pendingmt; + pendingmt = null; + } + + if (multitext != null) { + + // OK, we have text-like content to push back. + // and it still has values in it. + // advance the cursor + mtpos++; + + final Content ret = mttext[mtpos] == null + ? mtdata[mtpos] : null; + + + // we can calculate the hasnext + hasnext = mtpos + 1 < mtsize || + pending != null; + + // return null to indicate text content. + return ret; + } + + // non-text, increment and return content. + final Content ret = pending; + pending = content.hasNext() ? content.next() : null; + + // OK, we are returning some content. + // we need to determine the state of the next loop. + // cursor at this point has been advanced! + if (pending == null) { + hasnext = false; + } else { + // there is some more content. + // we need to inspect it to determine whether it is good + if (isTextLike(pending)) { + // calculate what this next text-like content looks like. + pendingmt = buildMultiText(false); + analyzeMultiText(pendingmt, 0, mtsourcesize); + pendingmt.done(); + + if (mtsize > 0) { + hasnext = true; + } else { + // all white text... perhaps we need indenting anyway. + // buildMultiText has moved on the pending value.... + if (pending != null && newlineindent != null) { + // yes, we need indenting. + // redefine the pending. + resetMultiText(); + pendingmt = holdingmt; + pendingmt.forceAppend(newlineindent); + pendingmt.done(); + hasnext = true; + } else { + pendingmt = null; + hasnext = pending != null; + } + } + } else { + // it is non-text content... we have more content. + // but, we just returned non-text content. We may need to indent + if (newlineindent != null) { + resetMultiText(); + pendingmt = holdingmt; + pendingmt.forceAppend(newlineindent); + pendingmt.done(); + } + hasnext = true; + } + } + return ret; + } + + private void resetMultiText() { + mtsourcesize = 0; + mtpos = -1; + mtsize = 0; + mtgottext = false; + mtpostpad = false; + mtwasescape = null; + mtbuffer.setLength(0); + } + + /** + * Add the content at the specified indices to the provided MultiText. + * @param mtext the MultiText to append to. + * @param offset The first Text-like content to add to the MultiText + * @param len The number of Text-like content items to add. + */ + protected abstract void analyzeMultiText(MultiText mtext, int offset, int len); + + /** + * Get the content at a position in the input content. Useful for subclasses + * in their {@link #analyzeMultiText(MultiText, int, int)} calls. + * @param index the index to get the content at. + * @return the content at the index. + */ + protected final Content get(final int index) { + return mtsource[index]; + } + + @Override + public final boolean isAllText() { + return alltext; + } + + @Override + public final boolean hasNext() { + return hasnext; + } + + /** + * This method was changed in 2.0.5 + * It now is only called when building the content of the variable pendingmt + * This is important, because only pendingmt can be referenced when analyzing + * the MultiText content. + * @param first + * @return The updated MultiText containing the correct sequence of Text-like content + */ + private final MultiText buildMultiText(final boolean first) { + // set up a sequence where the next bunch of stuff is text. + if (!first && newlineindent != null) { + mtbuffer.append(newlineindent); + } + mtsourcesize = 0; + do { + if (mtsourcesize >= mtsource.length) { + mtsource = ArrayCopy.copyOf(mtsource, mtsource.length * 2); + } + mtsource[mtsourcesize++] = pending; + pending = content.hasNext() ? content.next() : null; + } while (pending != null && isTextLike(pending)); + + mtpostpad = pending != null; + mtwasescape = fstack.getEscapeOutput(); + return holdingmt; + } + + @Override + public final String text() { + if (multitext == null || mtpos >= mtsize) { + return null; + } + return mttext[mtpos]; + } + + @Override + public final boolean isCDATA() { + if (multitext == null || mtpos >= mtsize) { + return false; + } + if (mttext[mtpos] == null) { + return false; + } + + return mtdata[mtpos] == CDATATOKEN; + } + + @Override + public final boolean isAllWhitespace() { + return allwhite; + } + + private final boolean isTextLike(final Content c) { + switch (c.getCType()) { + case Text: + case CDATA: + case EntityRef: + return true; + default: + // nothing. + } + return false; + } + + +} diff --git a/core/src/java/org/jdom/output/support/AbstractOutputProcessor.java b/core/src/java/org/jdom/output/support/AbstractOutputProcessor.java new file mode 100644 index 0000000..2959cb5 --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractOutputProcessor.java @@ -0,0 +1,113 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import org.jdom.Content; + +/** + * Methods common/useful for all Outputter processors. + * + * @since JDOM2 + * @author Rolf Lear + */ +public abstract class AbstractOutputProcessor { + + /* + * ======================================================================== + * Support methods for Text-content formatting. Should all be protected. The + * following are used when printing Text-based data. Because of complicated + * multi-sequential text sometimes the requirements are odd. All Text + * content will be output using these methods, which is why there is the None + * version. + * ======================================================================== + */ + + /** + * Create a walker to process Content List values. + *

+ * If you require a custom walker to process content in a specific way + * then you probably want to override this method to build the walker you + * want. + * + * @param fstack The current FormatStack for the walker (this should not be + * modified by the Walker). + * @param content The list of content to walk. + * @param escape If you want the Text values to be XMLEscaped then supply + * a non-null EscapeStrategy to use. + * @return the created walker. + */ + protected Walker buildWalker(final FormatStack fstack, + final List content, boolean escape) { + + switch (fstack.getTextMode()) { + case PRESERVE: + return new WalkerPRESERVE(content); + case NORMALIZE: + return new WalkerNORMALIZE(content, fstack, escape); + case TRIM: + return new WalkerTRIM(content, fstack, escape); + case TRIM_FULL_WHITE: + return new WalkerTRIM_FULL_WHITE(content, fstack, escape); + } + // all cases should be handled in the switch statement above. If someone + // creates a new TextMode though, then it will create a warning in + // eclipse above, and the code will fall through to this 'default' raw + // instance. + return new WalkerPRESERVE(content); + } + +} diff --git a/core/src/java/org/jdom/output/support/AbstractSAXOutputProcessor.java b/core/src/java/org/jdom/output/support/AbstractSAXOutputProcessor.java new file mode 100644 index 0000000..df283b2 --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractSAXOutputProcessor.java @@ -0,0 +1,839 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import static org.jdom.JDOMConstants.*; + +import java.io.IOException; +import java.io.StringReader; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; + +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.XMLOutputter2; +import org.jdom.util.NamespaceStack; + +/** + * Outputs a JDOM document as a stream of SAX2 events. + *

+ * Most ContentHandler callbacks are supported. Neither + * ignorableWhitespace() nor skippedEntity() have been + * implemented. + *

+ * At this time, it is not possible to access notations and unparsed entity + * references in a DTD from JDOM. Therefore, full DTDHandler + * call-backs have not been implemented yet. + *

+ * The ErrorHandler call-backs have not been implemented, since + * these are supposed to be invoked when the document is parsed and at this + * point the document exists in memory and is known to have no errors. + *

+ * The SAX2 API does not support whitespace formatting outside the root element. + * As a consequence any Formatting options that would normally affect the + * structures outside the root element will be ignored. + * + * @author Brett McLaughlin + * @author Jason Hunter + * @author Fred Trimble + * @author Bradley S. Huffman + * @author Rolf Lear + */ +public class AbstractSAXOutputProcessor extends AbstractOutputProcessor + implements SAXOutputProcessor { + + private static void locate(SAXTarget out) { + out.getContentHandler().setDocumentLocator(out.getLocator()); + } + + @Override + public void process(SAXTarget out, Format format, Document doc) + throws JDOMException { + try { + locate(out); + printDocument(out, new FormatStack(format), new NamespaceStack(), + doc); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the Document: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, DocType doctype) + throws JDOMException { + try { + locate(out); + printDocType(out, new FormatStack(format), doctype); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the DocType: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, Element element) + throws JDOMException { + try { + locate(out); + printElement(out, new FormatStack(format), new NamespaceStack(), + element); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the Element: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, + List list) throws JDOMException { + try { + locate(out); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + printContent(out, fstack, new NamespaceStack(), walker); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the List: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, CDATA cdata) + throws JDOMException { + try { + locate(out); + final List list = Collections.singletonList(cdata); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + printContent(out, fstack, new NamespaceStack(), walker); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the CDATA: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, Text text) + throws JDOMException { + try { + locate(out); + final List list = Collections.singletonList(text); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + printContent(out, fstack, new NamespaceStack(), walker); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the Text: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, Comment comment) + throws JDOMException { + try { + locate(out); + printComment(out, new FormatStack(format), comment); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the Comment: ", se); + } + } + + @Override + public void process(SAXTarget out, Format format, ProcessingInstruction pi) + throws JDOMException { + try { + locate(out); + printProcessingInstruction(out, new FormatStack(format), pi); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the ProcessingInstruction: ", + se); + } + } + + @Override + public void process(SAXTarget out, Format format, EntityRef entity) + throws JDOMException { + try { + locate(out); + printEntityRef(out, new FormatStack(format), entity); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the EntityRef: ", + se); + } + } + + @Override + public void processAsDocument(SAXTarget out, Format format, + List nodes) throws JDOMException { + try { + if ((nodes == null) || (nodes.size() == 0)) { + return; + } + + locate(out); + // contentHandler.setDocumentLocator() + out.getContentHandler().startDocument(); + + FormatStack fstack = new FormatStack(format); + + // Fire DTD events .. if there is a DocType node + if (out.isReportDTDEvents()) { + for (Content c : nodes) { + if (c instanceof DocType) { + printDocType(out, fstack, (DocType)c); + // fire only the first DocType's events + // subsequent ones are ignored. + break; + } + } + } + + Walker walker = buildWalker(fstack, nodes, false); + + printContent(out, fstack, new NamespaceStack(), walker); + + // contentHandler.endDocument() + out.getContentHandler().endDocument(); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the List: ", se); + } + } + + @Override + public void processAsDocument(SAXTarget out, Format format, Element node) + throws JDOMException { + try { + if (node == null) { + return; + } + + locate(out); + // contentHandler.setDocumentLocator() + out.getContentHandler().startDocument(); + + printElement(out, new FormatStack(format), new NamespaceStack(), + node); + + // contentHandler.endDocument() + out.getContentHandler().endDocument(); + } catch (SAXException se) { + throw new JDOMException( + "Encountered a SAX exception processing the Element: ", se); + } + } + + /* ******************************************* + * Support methods for output. Should all be protected. All content-type + * print methods have a FormatStack. Only printContent is responsible for + * outputting appropriate indenting and newlines, which are easily available + * using the FormatStack.getLevelIndent() and FormatStack.getLevelEOL(). + * ******************************************* + */ + + /** + * This will handle printing of a {@link Document}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param document + * Document to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printDocument(final SAXTarget out, final FormatStack fstack, + final NamespaceStack nstack, final Document document) + throws SAXException { + if (document == null) { + return; + } + + // contentHandler.startDocument() + out.getContentHandler().startDocument(); + + // Fire DTD events + if (out.isReportDTDEvents()) { + printDocType(out, fstack, document.getDocType()); + } + + // Handle root element, as well as any root level + // processing instructions and comments + // ignore DocType, if any. + final int sz = document.getContentSize(); + + if (sz > 0) { + for (int i = 0; i < sz; i++) { + final Content c = document.getContent(i); + out.getLocator().setNode(c); + switch (c.getCType()) { + case Comment : + printComment(out, fstack, (Comment) c); + break; + case DocType : + // cannot simply add a DocType to a SAX stream + // it is added when the Stream is created. + break; + case Element : + printElement(out, fstack, nstack, (Element)c); + break; + case ProcessingInstruction : + printProcessingInstruction(out, fstack, + (ProcessingInstruction)c); + break; + default : + // do nothing. + } + } + } + + // contentHandler.endDocument() + out.getContentHandler().endDocument(); + + } + + /** + * This will handle printing of a {@link DocType}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param docType + * DocType to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printDocType(final SAXTarget out, final FormatStack fstack, + final DocType docType) throws SAXException { + + // Fire DTD-related events only if handlers have been registered. + final DTDHandler dtdHandler = out.getDTDHandler(); + final DeclHandler declHandler = out.getDeclHandler(); + if ((docType != null) + && ((dtdHandler != null) || (declHandler != null))) { + + // Build a dummy XML document that only references the DTD... + String dtdDoc = new XMLOutputter2().outputString(docType); + + try { + // And parse it to fire DTD events. + createDTDParser(out).parse( + new InputSource(new StringReader(dtdDoc))); + + // We should never reach this point as the document is + // ill-formed; it does not have any root element. + } catch (SAXParseException e) { + // Expected exception: There's no root element in document. + } catch (IOException e) { + throw new SAXException("DTD parsing error", e); + } + } + + } + + /** + * This will handle printing of a {@link ProcessingInstruction}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param pi + * ProcessingInstruction to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printProcessingInstruction(final SAXTarget out, + final FormatStack fstack, final ProcessingInstruction pi) + throws SAXException { + out.getContentHandler().processingInstruction(pi.getTarget(), + pi.getData()); + } + + /** + * This will handle printing of a {@link Comment}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param comment + * Comment to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printComment(final SAXTarget out, final FormatStack fstack, + final Comment comment) throws SAXException { + if (out.getLexicalHandler() != null) { + char[] c = comment.getText().toCharArray(); + out.getLexicalHandler().comment(c, 0, c.length); + } + } + + /** + * This will handle printing of an {@link EntityRef}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param entity + * EntotyRef to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printEntityRef(final SAXTarget out, + final FormatStack fstack, final EntityRef entity) + throws SAXException { + out.getContentHandler().skippedEntity(entity.getName()); + } + + /** + * This will handle printing of a {@link CDATA}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param cdata + * CDATA to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printCDATA(final SAXTarget out, final FormatStack fstack, + final CDATA cdata) throws SAXException { + // CDATAs are treated like text, not indented/newline content. + final LexicalHandler lexicalHandler = out.getLexicalHandler(); + final char[] chars = cdata.getText().toCharArray(); + if (lexicalHandler != null) { + lexicalHandler.startCDATA(); + out.getContentHandler().characters(chars, 0, chars.length); + lexicalHandler.endCDATA(); + } else { + out.getContentHandler().characters(chars, 0, chars.length); + } + } + + /** + * This will handle printing of a {@link Text}. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param text + * Text to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printText(final SAXTarget out, final FormatStack fstack, + final Text text) throws SAXException { + final char[] chars = text.getText().toCharArray(); + out.getContentHandler().characters(chars, 0, chars.length); + } + + /** + * This will handle printing of an {@link Element}. + *

+ * This method arranges for outputting the Element infrastructure including + * Namespace Declarations and Attributes. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param element + * Element to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printElement(final SAXTarget out, final FormatStack fstack, + final NamespaceStack nstack, final Element element) + throws SAXException { + + final ContentHandler ch = out.getContentHandler(); + final Object origloc = out.getLocator().getNode(); + nstack.push(element); + try { + + // update locator + out.getLocator().setNode(element); + + AttributesImpl atts = new AttributesImpl(); + + // contentHandler.startPrefixMapping() + for (Namespace ns : nstack.addedForward()) { + ch.startPrefixMapping(ns.getPrefix(), ns.getURI()); + if (out.isDeclareNamespaces()) { + // add a physical attribute if requested. + String prefix = ns.getPrefix(); + if (prefix.equals("")) { + atts.addAttribute("", "", "xmlns", "CDATA", ns.getURI()); + } else { + atts.addAttribute("", "", "xmlns:" + ns.getPrefix(), + "CDATA", ns.getURI()); + } + } + } + + // Allocate attribute list. + if (element.hasAttributes()) { + for (Attribute a : element.getAttributes()) { + if (!a.isSpecified() && fstack.isSpecifiedAttributesOnly()) { + continue; + } + atts.addAttribute(a.getNamespaceURI(), a.getName(), + a.getQualifiedName(), + getAttributeTypeName(a.getAttributeType()), + a.getValue()); + } + } + + // contentHandler.startElement() + ch.startElement(element.getNamespaceURI(), element.getName(), + element.getQualifiedName(), atts); + + final List content = element.getContent(); + + // OK, now we print out the meat of the Element + if (!content.isEmpty()) { + TextMode textmode = fstack.getTextMode(); + + // Check for xml:space and adjust format settings + final String space = element.getAttributeValue("space", + Namespace.XML_NAMESPACE); + + if ("default".equals(space)) { + textmode = fstack.getDefaultMode(); + } else if ("preserve".equals(space)) { + textmode = TextMode.PRESERVE; + } + + fstack.push(); + try { + fstack.setTextMode(textmode); + Walker walker = buildWalker(fstack, content, false); + if (walker.hasNext()) { + + if (!walker.isAllText() + && fstack.getPadBetween() != null) { + // we need to newline/indent + final String indent = fstack.getPadBetween(); + printText(out, fstack, new Text(indent)); + } + + printContent(out, fstack, nstack, walker); + + if (!walker.isAllText() && + fstack.getPadLast() != null) { + // we need to newline/indent + final String indent = + fstack.getPadLast(); + printText(out, fstack, new Text(indent)); + } + + } + + } finally { + fstack.pop(); + } + } + + // contentHandler.endElement() + out.getContentHandler().endElement(element.getNamespaceURI(), + element.getName(), element.getQualifiedName()); + + // contentHandler.endPrefixMapping() + // de-map in reverse order to the mapping. + for (Namespace ns : nstack.addedReverse()) { + ch.endPrefixMapping(ns.getPrefix()); + } + + } finally { + nstack.pop(); + out.getLocator().setNode(origloc); + } + } + + /** + * This will handle printing of a List of {@link Content}. + *

+ * It relies on the appropriate Walker to get the formatting right. + * + * @param out + * SAXTarget to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param walker + * Waker of Content to write. + * @throws SAXException + * if the destination SAXTarget fails + */ + protected void printContent(final SAXTarget out, final FormatStack fstack, + final NamespaceStack nstack, final Walker walker) + throws SAXException { + + while (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + // Formatted Text or CDATA + final String text = walker.text(); + if (walker.isCDATA()) { + printCDATA(out, fstack, new CDATA(text)); + } else { + printText(out, fstack, new Text(text)); + } + } else { + switch (c.getCType()) { + case CDATA: + printCDATA(out, fstack, (CDATA)c); + break; + case Comment: + printComment(out, fstack, (Comment)c); + break; + case DocType: + // do nothing. + break; + case Element : + printElement(out, fstack, nstack, (Element)c); + break; + case EntityRef: + printEntityRef(out, fstack, (EntityRef)c); + break; + case ProcessingInstruction: + printProcessingInstruction(out, fstack, + (ProcessingInstruction)c); + break; + case Text: + printText(out, fstack, (Text)c); + break; + } + } + } + } + + /** + *

+ * Returns the SAX 2.0 attribute type string from the type of a JDOM + * Attribute. + *

+ * + * @param type + * int the type of the JDOM attribute. + * @return String the SAX 2.0 attribute type string. + * @see org.jdom.Attribute#getAttributeType + * @see org.xml.sax.Attributes#getType + */ + private static String getAttributeTypeName(AttributeType type) { + switch (type) { + case UNDECLARED: + return "CDATA"; + default: + return type.name(); + } + } + + /** + *

+ * Creates a SAX XMLReader. + *

+ * + * @return XMLReader a SAX2 parser. + * @throws Exception + * if no parser can be created. + */ + protected XMLReader createParser() throws Exception { + XMLReader parser = null; + + // Try using JAXP... + // Note we need JAXP 1.1, and if JAXP 1.0 is all that's + // available then the getXMLReader call fails and we skip + // to the hard coded default parser + try { + Class factoryClass = Class + .forName("javax.xml.parsers.SAXParserFactory"); + + // factory = SAXParserFactory.newInstance(); + Method newParserInstance = factoryClass.getMethod("newInstance"); + Object factory = newParserInstance.invoke(null); + + // jaxpParser = factory.newSAXParser(); + Method newSAXParser = factoryClass.getMethod("newSAXParser"); + Object jaxpParser = newSAXParser.invoke(factory); + + // parser = jaxpParser.getXMLReader(); + Class parserClass = jaxpParser.getClass(); + Method getXMLReader = parserClass.getMethod("getXMLReader"); + parser = (XMLReader) getXMLReader.invoke(jaxpParser); + } catch (ClassNotFoundException e) { + // e.printStackTrace(); + } catch (InvocationTargetException e) { + // e.printStackTrace(); + } catch (NoSuchMethodException e) { + // e.printStackTrace(); + } catch (IllegalAccessException e) { + // e.printStackTrace(); + } + + // Check to see if we got a parser yet, if not, try to use a + // hard coded default + if (parser == null) { + parser = XMLReaderFactory + .createXMLReader("org.apache.xerces.parsers.SAXParser"); + } + return parser; + } + + /** + *

+ * This will create a SAX XMLReader capable of parsing a DTD and configure + * it so that the DTD parsing events are routed to the handlers registered + * onto this SAXOutputter. + *

+ * + * @return XMLReader a SAX2 parser. + * @throws JDOMException + * if no parser can be created. + */ + private XMLReader createDTDParser(SAXTarget out) throws SAXException { + XMLReader parser = null; + + // Get a parser instance + try { + parser = createParser(); + } catch (Exception ex1) { + throw new SAXException("Error in SAX parser allocation", ex1); + } + + // Register handlers + if (out.getDTDHandler() != null) { + parser.setDTDHandler(out.getDTDHandler()); + } + if (out.getEntityResolver() != null) { + parser.setEntityResolver(out.getEntityResolver()); + } + if (out.getLexicalHandler() != null) { + try { + parser.setProperty(SAX_PROPERTY_LEXICAL_HANDLER, + out.getLexicalHandler()); + } catch (SAXException ex1) { + try { + parser.setProperty(SAX_PROPERTY_LEXICAL_HANDLER_ALT, + out.getLexicalHandler()); + } catch (SAXException ex2) { + // Forget it! + } + } + } + if (out.getDeclHandler() != null) { + try { + parser.setProperty(SAX_PROPERTY_DECLARATION_HANDLER, + out.getDeclHandler()); + } catch (SAXException ex1) { + try { + parser.setProperty(SAX_PROPERTY_DECLARATION_HANDLER_ALT, + out.getDeclHandler()); + } catch (SAXException ex2) { + // Forget it! + } + } + } + + // Absorb errors as much as possible, per Laurent + parser.setErrorHandler(new DefaultHandler()); + + return parser; + } + +} diff --git a/core/src/java/org/jdom/output/support/AbstractStAXEventProcessor.java b/core/src/java/org/jdom/output/support/AbstractStAXEventProcessor.java new file mode 100644 index 0000000..854e5a3 --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractStAXEventProcessor.java @@ -0,0 +1,776 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.util.XMLEventConsumer; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Content.CType; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.Verifier; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.StAXEventOutputter; +import org.jdom.util.NamespaceStack; + +/** + * This class provides a concrete implementation of {@link StAXEventProcessor} + * for supporting the {@link StAXEventOutputter}. + *

+ *

Overview

+ *

+ * This class is marked abstract even though all methods are fully implemented. + * The process*(...) methods are public because they match the + * StAXEventProcessor interface but the remaining methods are all protected. + *

+ * People who want to create a custom StAXEventProcessor for StAXEventOutputter are + * able to extend this class and modify any functionality they want. Before + * sub-classing this you should first check to see if the {@link Format} class + * can get you the results you want. + *

+ * Subclasses of this should have reentrant methods. This is + * easiest to accomplish simply by not allowing any instance fields. If your + * sub-class has an instance field/variable, then it's probably broken. + *

+ *

The Stacks

+ *

+ * One significant feature of this implementation is that it creates and + * maintains both a {@link NamespaceStack} and {@link FormatStack} that are + * managed in the + * {@link #printElement(XMLEventConsumer, FormatStack, NamespaceStack, XMLEventFactory, Element)} method. + * The stacks are pushed and popped in that method only. They significantly + * improve the performance and readability of the code. + *

+ * The NamespaceStack is only sent through to the + * {@link #printElement(XMLEventConsumer, FormatStack, NamespaceStack, XMLEventFactory, Element)} and + * {@link #printContent(XMLEventConsumer, FormatStack, NamespaceStack, XMLEventFactory, Walker)} methods, but + * the FormatStack is pushed through to all print* Methods. + * + * @see StAXEventOutputter + * @see StAXEventProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public abstract class AbstractStAXEventProcessor extends AbstractOutputProcessor + implements StAXEventProcessor { + + private static final class NSIterator implements Iterator { + private final Iterator source; + private final XMLEventFactory fac; + + public NSIterator(Iterator source, XMLEventFactory fac) { + super(); + this.source = source; + this.fac = fac; + } + + @Override + public boolean hasNext() { + return source.hasNext(); + } + + @Override + public javax.xml.stream.events.Namespace next() { + Namespace ns = source.next(); + return fac.createNamespace(ns.getPrefix(), ns.getURI()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot remove Namespaces"); + + } + + } + private static final class AttIterator implements Iterator { + private final Iterator source; + private final XMLEventFactory fac; + + public AttIterator(final Iterator source, final XMLEventFactory fac, + final boolean specifiedAttributesOnly) { + super(); + // remove not-specified Attributes if needed.... + this.source = specifiedAttributesOnly ? specified(source) : source; + this.fac = fac; + } + + private Iterator specified(Iterator src) { + if (src == null) { + return null; + } + final ArrayList al = new ArrayList(); + while (src.hasNext()) { + Attribute att = src.next(); + if (att.isSpecified()) { + al.add(att); + } + } + return al.isEmpty() ? null : al.iterator(); + } + + @Override + public boolean hasNext() { + return source != null && source.hasNext(); + } + + @Override + public javax.xml.stream.events.Attribute next() { + final Attribute att = source.next(); + final Namespace ns = att.getNamespace(); + if (ns == Namespace.NO_NAMESPACE) { + return fac.createAttribute(att.getName(), att.getValue()); + } + return fac.createAttribute(ns.getPrefix(), ns.getURI(), + att.getName(), att.getValue()); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot remove attributes"); + + } + + } + + + + /* ******************************************* + * StAXEventProcessor implementation. + * ******************************************* + */ + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.Document, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final Document doc) throws XMLStreamException { + printDocument(out, new FormatStack(format), new NamespaceStack(), eventfactory, doc); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.DocType, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final DocType doctype) throws XMLStreamException { + printDocType(out, new FormatStack(format), eventfactory, doctype); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.Element, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final Element element) throws XMLStreamException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + printElement(out, new FormatStack(format), new NamespaceStack(), + eventfactory, element); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * java.util.List, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final List list) + throws XMLStreamException { + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + printContent(out, new FormatStack(format), new NamespaceStack(), eventfactory, walker); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.CDATA, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final CDATA cdata) throws XMLStreamException { + final List list = Collections.singletonList(cdata); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + printCDATA(out, fstack, eventfactory, new CDATA(walker.text())); + } else if (c.getCType() == CType.CDATA) { + printCDATA(out, fstack, eventfactory, (CDATA)c); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.Text, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final Text text) throws XMLStreamException { + final List list = Collections.singletonList(text); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + printText(out, fstack, eventfactory, new Text(walker.text())); + } else if (c.getCType() == CType.Text) { + printText(out, fstack, eventfactory, (Text)c); + } + } + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.Comment, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final Comment comment) throws XMLStreamException { + printComment(out, new FormatStack(format), eventfactory, comment); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.ProcessingInstruction, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final ProcessingInstruction pi) throws XMLStreamException { + FormatStack fstack = new FormatStack(format); + // Output PI verbatim, disregarding TrAX escaping PIs. + fstack.setIgnoreTrAXEscapingPIs(true); + printProcessingInstruction(out, fstack, eventfactory, pi); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXEventProcessor#process(java.io.XMLEventConsumer, + * org.jdom.EntityRef, org.jdom.output.Format) + */ + @Override + public void process(final XMLEventConsumer out, final Format format, + final XMLEventFactory eventfactory, final EntityRef entity) throws XMLStreamException { + printEntityRef(out, new FormatStack(format), eventfactory, entity); + } + + /* ******************************************* + * Support methods for output. Should all be protected. All content-type + * print methods have a FormatStack. Only printContent is responsible for + * outputting appropriate indenting and newlines, which are easily available + * using the FormatStack.getLevelIndent() and FormatStack.getLevelEOL(). + * ******************************************* + */ + + /** + * This will handle printing of a {@link Document}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param doc + * Document to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printDocument(final XMLEventConsumer out, final FormatStack fstack, + final NamespaceStack nstack, final XMLEventFactory eventfactory, final Document doc) throws XMLStreamException { + + if (fstack.isOmitDeclaration()) { + // this actually writes the declaration as version 1, UTF-8 + out.add(eventfactory.createStartDocument(null, null)); + } else if (fstack.isOmitEncoding()) { + out.add(eventfactory.createStartDocument(null, "1.0")); + if (fstack.getLineSeparator() != null) { + out.add(eventfactory.createCharacters(fstack.getLineSeparator())); + } + } else { + out.add(eventfactory.createStartDocument(fstack.getEncoding(), "1.0")); + if (fstack.getLineSeparator() != null) { + out.add(eventfactory.createCharacters(fstack.getLineSeparator())); + } + } + + // If there is no root element then we cannot use the normal ways to + // access the ContentList because Document throws an exception. + // so we hack it and just access it by index. + List list = doc.hasRootElement() ? doc.getContent() : + new ArrayList(doc.getContentSize()); + if (list.isEmpty()) { + final int sz = doc.getContentSize(); + for (int i = 0; i < sz; i++) { + list.add(doc.getContent(i)); + } + } + + Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + while (walker.hasNext()) { + + final Content c = walker.next(); + + // we do not ignore Text-like things in the Document. + // the walker creates the indenting for us. + if (c == null) { + // but, what we do is ensure it is all whitespace, and not CDATA + final String padding = walker.text(); + if (padding != null && Verifier.isAllXMLWhitespace(padding) && + !walker.isCDATA()) { + // we do not use the escaping or text* method because this + // content is outside of the root element, and thus is not + // strict text. + out.add(eventfactory.createCharacters(padding)); + } + } else { + switch (c.getCType()) { + case Comment : + printComment(out, fstack, eventfactory, (Comment) c); + break; + case DocType : + printDocType(out, fstack, eventfactory, (DocType)c); + break; + case Element : + printElement(out, fstack, nstack, eventfactory, + (Element)c); + break; + case ProcessingInstruction : + printProcessingInstruction(out, fstack, eventfactory, + (ProcessingInstruction)c); + break; + default : + // do nothing. + } + } + + } + + if (fstack.getLineSeparator() != null) { + out.add(eventfactory.createCharacters(fstack.getLineSeparator())); + } + } + + out.add(eventfactory.createEndDocument()); + + } + + /** + * This will handle printing of a {@link DocType}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param docType + * DocType to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printDocType(final XMLEventConsumer out, final FormatStack fstack, + final XMLEventFactory eventfactory, final DocType docType) throws XMLStreamException { + + final String publicID = docType.getPublicID(); + final String systemID = docType.getSystemID(); + final String internalSubset = docType.getInternalSubset(); + boolean hasPublic = false; + + // Declaration is never indented. + // write(out, fstack.getLevelIndent()); + + StringWriter sw = new StringWriter(); + + sw.write(""); + + // DocType does not write it's own EOL + // for compatibility reasons. Only + // when output from inside a Content set. + // write(out, fstack.getLineSeparator()); + out.add(eventfactory.createDTD(sw.toString())); + } + + /** + * This will handle printing of a {@link ProcessingInstruction}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param pi + * ProcessingInstruction to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printProcessingInstruction(final XMLEventConsumer out, + final FormatStack fstack, final XMLEventFactory eventfactory, final ProcessingInstruction pi) + throws XMLStreamException { + String target = pi.getTarget(); + String rawData = pi.getData(); + if (rawData != null && rawData.trim().length() > 0) { + out.add(eventfactory.createProcessingInstruction(target, rawData)); + } else { + out.add(eventfactory.createProcessingInstruction(target, "")); + } + } + + /** + * This will handle printing of a {@link Comment}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param comment + * Comment to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printComment(final XMLEventConsumer out, final FormatStack fstack, + final XMLEventFactory eventfactory, final Comment comment) throws XMLStreamException { + out.add(eventfactory.createComment(comment.getText())); + } + + /** + * This will handle printing of an {@link EntityRef}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param entity + * EntotyRef to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printEntityRef(final XMLEventConsumer out, final FormatStack fstack, + final XMLEventFactory eventfactory, final EntityRef entity) throws XMLStreamException { + out.add(eventfactory.createEntityReference(entity.getName(), null)); + } + + /** + * This will handle printing of a {@link CDATA}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param cdata + * CDATA to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printCDATA(final XMLEventConsumer out, final FormatStack fstack, + final XMLEventFactory eventfactory, final CDATA cdata) throws XMLStreamException { + // CDATAs are treated like text, not indented/newline content. + out.add(eventfactory.createCData(cdata.getText())); + } + + /** + * This will handle printing of a {@link Text}. + * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param text + * Text to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printText(final XMLEventConsumer out, final FormatStack fstack, + final XMLEventFactory eventfactory, final Text text) throws XMLStreamException { + out.add(eventfactory.createCharacters(text.getText())); + } + + /** + * This will handle printing of an {@link Element}. + *

+ * This method arranges for outputting the Element infrastructure including + * Namespace Declarations and Attributes. + *

+ * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param element + * Element to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printElement(final XMLEventConsumer out, final FormatStack fstack, + final NamespaceStack nstack, final XMLEventFactory eventfactory, + final Element element) throws XMLStreamException { + + nstack.push(element); + try { + + Namespace ns = element.getNamespace(); + Iterator ait = element.hasAttributes() ? + element.getAttributes().iterator() : + null; + if (ns == Namespace.NO_NAMESPACE) { + out.add(eventfactory.createStartElement("", "", element.getName(), + new AttIterator(ait, eventfactory, fstack.isSpecifiedAttributesOnly()), + new NSIterator(nstack.addedForward().iterator(), eventfactory))); + } else if ("".equals(ns.getPrefix())) { + out.add(eventfactory.createStartElement("", ns.getURI(), element.getName(), + new AttIterator(ait, eventfactory, fstack.isSpecifiedAttributesOnly()), + new NSIterator(nstack.addedForward().iterator(), eventfactory))); + } else { + out.add(eventfactory.createStartElement(ns.getPrefix(), ns.getURI(), element.getName(), + new AttIterator(ait, eventfactory, fstack.isSpecifiedAttributesOnly()), + new NSIterator(nstack.addedForward().iterator(), eventfactory))); + } + ait = null; + + final List content = element.getContent(); + + if (!content.isEmpty()) { + TextMode textmode = fstack.getTextMode(); + + // Check for xml:space and adjust format settings + final String space = element.getAttributeValue("space", + Namespace.XML_NAMESPACE); + + if ("default".equals(space)) { + textmode = fstack.getDefaultMode(); + } + else if ("preserve".equals(space)) { + textmode = TextMode.PRESERVE; + } + + fstack.push(); + try { + + fstack.setTextMode(textmode); + + final Walker walker = buildWalker(fstack, content, false); + if (walker.hasNext()) { + if (!walker.isAllText() && fstack.getPadBetween() != null) { + // we need to newline/indent + final String indent = fstack.getPadBetween(); + printText(out, fstack, eventfactory, new Text(indent)); + } + + printContent(out, fstack, nstack, eventfactory, walker); + + if (!walker.isAllText() && fstack.getPadLast() != null) { + // we need to newline/indent + final String indent = fstack.getPadLast(); + printText(out, fstack, eventfactory, new Text(indent)); + } + } + } finally { + fstack.pop(); + } + + } + + out.add(eventfactory.createEndElement(element.getNamespacePrefix(), + element.getNamespaceURI(), element.getName(), + new NSIterator(nstack.addedReverse().iterator(), eventfactory))); + + + } finally { + nstack.pop(); + } + } + + /** + * This will handle printing of a List of {@link Content}. + *

+ * + * @param out + * XMLEventConsumer to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param eventfactory + * The XMLEventFactory for creating XMLEvents + * @param walker + * Walker of Content to write. + * @throws XMLStreamException + * if the destination XMLEventConsumer fails + */ + protected void printContent(final XMLEventConsumer out, + final FormatStack fstack, final NamespaceStack nstack, + final XMLEventFactory eventfactory, final Walker walker) + throws XMLStreamException { + + while (walker.hasNext()) { + + final Content content = walker.next(); + + if (content == null) { + if (walker.isCDATA()) { + printCDATA(out, fstack, eventfactory, new CDATA(walker.text())); + } else { + printText(out, fstack, eventfactory, new Text(walker.text())); + } + } else { + switch (content.getCType()) { + case CDATA: + printCDATA(out, fstack, eventfactory, (CDATA) content); + break; + case Comment: + printComment(out, fstack, eventfactory, (Comment) content); + break; + case Element: + printElement(out, fstack, nstack, eventfactory, (Element) content); + break; + case EntityRef: + printEntityRef(out, fstack, eventfactory, (EntityRef) content); + break; + case ProcessingInstruction: + printProcessingInstruction(out, fstack, eventfactory, + (ProcessingInstruction) content); + break; + case Text: + printText(out, fstack, eventfactory, (Text) content); + break; + case DocType: + printDocType(out, fstack, eventfactory, (DocType) content); + break; + default: + throw new IllegalStateException( + "Unexpected Content " + content.getCType()); + + } + } + } + } +} diff --git a/core/src/java/org/jdom/output/support/AbstractStAXStreamProcessor.java b/core/src/java/org/jdom/output/support/AbstractStAXStreamProcessor.java new file mode 100644 index 0000000..c023d94 --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractStAXStreamProcessor.java @@ -0,0 +1,814 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.JDOMConstants; +import org.jdom.Content.CType; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.Verifier; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.StAXStreamOutputter; +import org.jdom.util.NamespaceStack; + +/** + * This class provides a concrete implementation of {@link StAXStreamProcessor} + * for supporting the {@link StAXStreamOutputter}. + *

+ *

Overview

+ *

+ * This class is marked abstract even though all methods are fully implemented. + * The process*(...) methods are public because they match the + * StAXStreamProcessor interface but the remaining methods are all protected. + *

+ * People who want to create a custom StAXStreamProcessor for StAXStreamOutputter are + * able to extend this class and modify any functionality they want. Before + * sub-classing this you should first check to see if the {@link Format} class + * can get you the results you want. + *

+ * Subclasses of this should have reentrant methods. This is + * easiest to accomplish simply by not allowing any instance fields. If your + * sub-class has an instance field/variable, then it's probably broken. + *

+ *

The Stacks

+ *

+ * One significant feature of this implementation is that it creates and + * maintains both a {@link NamespaceStack} and {@link FormatStack} that are + * managed in the + * {@link #printElement(XMLStreamWriter, FormatStack, NamespaceStack, Element)} method. + * The stacks are pushed and popped in that method only. They significantly + * improve the performance and readability of the code. + *

+ * The NamespaceStack is only sent through to the + * {@link #printElement(XMLStreamWriter, FormatStack, NamespaceStack, Element)} and + * {@link #printContent(XMLStreamWriter, FormatStack, NamespaceStack, Walker)} methods, but + * the FormatStack is pushed through to all print* Methods. + *

+ * + * @see StAXStreamOutputter + * @see StAXStreamProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public abstract class AbstractStAXStreamProcessor + extends AbstractOutputProcessor implements StAXStreamProcessor { + + + /* ******************************************* + * StAXStreamProcessor implementation. + * ******************************************* + */ + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.Document, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final Document doc) throws XMLStreamException { + printDocument(out, new FormatStack(format), new NamespaceStack(), doc); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.DocType, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final DocType doctype) throws XMLStreamException { + printDocType(out, new FormatStack(format), doctype); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.Element, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final Element element) throws XMLStreamException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + printElement(out, new FormatStack(format), new NamespaceStack(), + element); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * java.util.List, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final List list) + throws XMLStreamException { + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + printContent(out, fstack, new NamespaceStack(), walker); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.CDATA, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final CDATA cdata) throws XMLStreamException { + final List list = Collections.singletonList(cdata); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + printCDATA(out, fstack, new CDATA(walker.text())); + } else if (c.getCType() == CType.CDATA) { + printCDATA(out, fstack, (CDATA)c); + } + } + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.Text, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final Text text) throws XMLStreamException { + final List list = Collections.singletonList(text); + final FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + final Content c = walker.next(); + if (c == null) { + printText(out, fstack, new Text(walker.text())); + } else if (c.getCType() == CType.Text) { + printText(out, fstack, (Text)c); + } + } + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.Comment, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final Comment comment) throws XMLStreamException { + printComment(out, new FormatStack(format), comment); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.ProcessingInstruction, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final ProcessingInstruction pi) throws XMLStreamException { + FormatStack fstack = new FormatStack(format); + // Output PI verbatim, disregarding TrAX escaping PIs. + fstack.setIgnoreTrAXEscapingPIs(true); + printProcessingInstruction(out, fstack, pi); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.StAXStreamProcessor#process(java.io.XMLStreamWriter, + * org.jdom.EntityRef, org.jdom.output.Format) + */ + @Override + public void process(final XMLStreamWriter out, final Format format, + final EntityRef entity) throws XMLStreamException { + printEntityRef(out, new FormatStack(format), entity); + out.flush(); + } + + /* ******************************************* + * Support methods for output. Should all be protected. All content-type + * print methods have a FormatStack. Only printContent is responsible for + * outputting appropriate indenting and newlines, which are easily available + * using the FormatStack.getLevelIndent() and FormatStack.getLevelEOL(). + * ******************************************* + */ + + /** + * This will handle printing of a {@link Document}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param doc + * Document to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printDocument(final XMLStreamWriter out, final FormatStack fstack, + final NamespaceStack nstack, final Document doc) throws XMLStreamException { + + if (fstack.isOmitDeclaration()) { + // this actually writes the declaration as version 1, UTF-8 + out.writeStartDocument(); + if (fstack.getLineSeparator() != null) { + out.writeCharacters(fstack.getLineSeparator()); + } + } else if (fstack.isOmitEncoding()) { + out.writeStartDocument("1.0"); + if (fstack.getLineSeparator() != null) { + out.writeCharacters(fstack.getLineSeparator()); + } + } else { + out.writeStartDocument(fstack.getEncoding(), "1.0"); + if (fstack.getLineSeparator() != null) { + out.writeCharacters(fstack.getLineSeparator()); + } + } + + // we can output characters outside the Root element in StAX Event + // code... so, take advantage. + // If there is no root element then we cannot use the normal ways to + // access the ContentList because Document throws an exception. + // so we hack it and just access it by index. + List list = doc.hasRootElement() ? doc.getContent() : + new ArrayList(doc.getContentSize()); + if (list.isEmpty()) { + final int sz = doc.getContentSize(); + for (int i = 0; i < sz; i++) { + list.add(doc.getContent(i)); + } + } + Walker walker = buildWalker(fstack, list, false); + if (walker.hasNext()) { + while (walker.hasNext()) { + + final Content c = walker.next(); + // we do not ignore Text-like things in the Document. + // the walker creates the indenting for us. + if (c == null) { + // but, what we do is ensure it is all whitespace, and not CDATA + final String padding = walker.text(); + if (padding != null && Verifier.isAllXMLWhitespace(padding) && + !walker.isCDATA()) { + // we do not use the escaping or text* method because this + // content is outside of the root element, and thus is not + // strict text. + out.writeCharacters(padding); + } + } else { + switch (c.getCType()) { + case Comment : + printComment(out, fstack, (Comment)c); + break; + case DocType : + printDocType(out, fstack, (DocType)c); + break; + case Element : + printElement(out, fstack, nstack, (Element)c); + break; + case ProcessingInstruction : + printProcessingInstruction(out, fstack, + (ProcessingInstruction)c); + break; + case Text : + final String padding = ((Text)c).getText(); + if (padding != null && Verifier.isAllXMLWhitespace(padding)) { + // we do not use the escaping or text* method because this + // content is outside of the root element, and thus is not + // strict text. + out.writeCharacters(padding); + } + default : + // do nothing. + } + } + + } + + if (fstack.getLineSeparator() != null) { + out.writeCharacters(fstack.getLineSeparator()); + } + } + + out.writeEndDocument(); + + } + + /** + * This will handle printing of a {@link DocType}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param docType + * DocType to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printDocType(final XMLStreamWriter out, final FormatStack fstack, + final DocType docType) throws XMLStreamException { + + final String publicID = docType.getPublicID(); + final String systemID = docType.getSystemID(); + final String internalSubset = docType.getInternalSubset(); + boolean hasPublic = false; + + // Declaration is never indented. + // write(out, fstack.getLevelIndent()); + + StringWriter sw = new StringWriter(); + + sw.write(""); + + // DocType does not write it's own EOL + // for compatibility reasons. Only + // when output from inside a Content set. + // write(out, fstack.getLineSeparator()); + out.writeDTD(sw.toString()); + } + + /** + * This will handle printing of a {@link ProcessingInstruction}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param pi + * ProcessingInstruction to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printProcessingInstruction(final XMLStreamWriter out, + final FormatStack fstack, final ProcessingInstruction pi) + throws XMLStreamException { + String target = pi.getTarget(); + String rawData = pi.getData(); + if (rawData != null && rawData.trim().length() > 0) { + out.writeProcessingInstruction(target, rawData); + } else { + out.writeProcessingInstruction(target); + } + } + + /** + * This will handle printing of a {@link Comment}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param comment + * Comment to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printComment(final XMLStreamWriter out, final FormatStack fstack, + final Comment comment) throws XMLStreamException { + out.writeComment(comment.getText()); + } + + /** + * This will handle printing of an {@link EntityRef}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param entity + * EntotyRef to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printEntityRef(final XMLStreamWriter out, final FormatStack fstack, + final EntityRef entity) throws XMLStreamException { + out.writeEntityRef(entity.getName()); + } + + /** + * This will handle printing of a {@link CDATA}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param cdata + * CDATA to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printCDATA(final XMLStreamWriter out, final FormatStack fstack, + final CDATA cdata) throws XMLStreamException { + // CDATAs are treated like text, not indented/newline content. + out.writeCData(cdata.getText()); + } + + /** + * This will handle printing of a {@link Text}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param text + * Text to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printText(final XMLStreamWriter out, final FormatStack fstack, + final Text text) throws XMLStreamException { + out.writeCharacters(text.getText()); + } + + /** + * This will handle printing of an {@link Element}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param element + * Element to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printElement(final XMLStreamWriter out, final FormatStack fstack, + final NamespaceStack nstack, final Element element) throws XMLStreamException { + + final ArrayList restore = new ArrayList(); + nstack.push(element); + try { + for (Namespace nsa : nstack.addedForward()) { + restore.add(nsa.getPrefix()); + if (JDOMConstants.NS_PREFIX_DEFAULT.equals(nsa.getPrefix())) { + out.setDefaultNamespace(nsa.getURI()); + } else { + out.setPrefix(nsa.getPrefix(), nsa.getURI()); + } + } + + final List content = element.getContent(); + + TextMode textmode = fstack.getTextMode(); + + + // OK, we play silly-buggers with the walker here in this class. + // we need to know what sort of Element we have, expanded or not. + // as a result, we need to know if we have content or not, and + // the walker can be empty if it is all formatted out. + // so we need to know the walker before we start the element. + // if the walker resolves to nothing then we set it to null + // as an indication that there is not sub-content. + Walker walker = null; + + if (!content.isEmpty()) { + + // Check for xml:space and adjust format settings + final String space = element.getAttributeValue("space", + Namespace.XML_NAMESPACE); + + if ("default".equals(space)) { + textmode = fstack.getDefaultMode(); + } + else if ("preserve".equals(space)) { + textmode = TextMode.PRESERVE; + } + + fstack.push(); + try { + fstack.setTextMode(textmode); + walker = buildWalker(fstack, content, false); + if (!walker.hasNext()) { + // rip out the walker if there is no content. + walker = null; + } + } finally { + fstack.pop(); + } + } + + // Three conditions that determine the required output. + // do we have an expanded element( or an single + // if there is any printable content, or if expandempty is set + // then we must expand. + boolean expandit = walker != null || fstack.isExpandEmptyElements(); + + final Namespace ns = element.getNamespace(); + if (expandit) { + out.writeStartElement(ns.getPrefix(), element.getName(), ns.getURI()); + + // Print the element's namespace, if appropriate + for (final Namespace nsd : nstack.addedForward()) { + printNamespace(out, fstack, nsd); + } + + // Print out attributes + if (element.hasAttributes()) { + for (final Attribute attribute : element.getAttributes()) { + printAttribute(out, fstack, attribute); + } + } + + // This neatens up the output stream for some reason - bug in standard StAX + // implementation requires us to close off the Element start tag before we + // start adding new Namespaces to child contexts... + out.writeCharacters(""); + + // OK, now we print out the meat of the Element + if (walker != null) { + // we need to re-create the walker/fstack. + fstack.push(); + try { + fstack.setTextMode(textmode); + if (!walker.isAllText() && fstack.getPadBetween() != null) { + // we need to newline/indent + final String indent = fstack.getPadBetween(); + printText(out, fstack, new Text(indent)); + } + + printContent(out, fstack, nstack, walker); + + if (!walker.isAllText() && fstack.getPadLast() != null) { + // we need to newline/indent + final String indent = fstack.getPadLast(); + printText(out, fstack, new Text(indent)); + } + } finally { + fstack.pop(); + } + } + + out.writeEndElement(); + + } else { + // implies: + // fstack.isExpandEmpty... is false + // and content.isEmpty() + // or textonly == true + // and preserve == false + // and whiteonly == true + + out.writeEmptyElement(ns.getPrefix(), element.getName(), ns.getURI()); + + // Print the element's namespace, if appropriate + for (final Namespace nsd : nstack.addedForward()) { + printNamespace(out, fstack, nsd); + } + + // Print out attributes + for (final Attribute attribute : element.getAttributes()) { + printAttribute(out, fstack, attribute); + } + // This neatens up the output stream for some reason. + out.writeCharacters(""); + } + + } finally { + nstack.pop(); + for (String pfx : restore) { + for (final Namespace nsa : nstack) { + if (nsa.getPrefix().equals(pfx)) { + if (JDOMConstants.NS_PREFIX_DEFAULT.equals(nsa.getPrefix())) { + out.setDefaultNamespace(nsa.getURI()); + } else { + out.setPrefix(nsa.getPrefix(), nsa.getURI()); + } + break; + } + } + } + + } + } + + /** + * This will handle printing of a List of {@link Content}. + *

+ * + * @param out + * XMLStreamWriter to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param walker + * Walker of Content to write. + * @throws XMLStreamException + * if the destination XMLStreamWriter fails + */ + protected void printContent(final XMLStreamWriter out, + final FormatStack fstack, final NamespaceStack nstack, + final Walker walker) throws XMLStreamException { + + while (walker.hasNext()) { + final Content content = walker.next(); + + if (content == null) { + if (walker.isCDATA()) { + printCDATA(out, fstack, new CDATA(walker.text())); + } else { + printText(out, fstack, new Text(walker.text())); + } + } else { + switch (content.getCType()) { + case CDATA: + printCDATA(out, fstack, (CDATA) content); + break; + case Comment: + printComment(out, fstack, (Comment) content); + break; + case Element: + printElement(out, fstack, nstack, (Element) content); + break; + case EntityRef: + printEntityRef(out, fstack, (EntityRef) content); + break; + case ProcessingInstruction: + printProcessingInstruction(out, fstack, + (ProcessingInstruction) content); + break; + case Text: + printText(out, fstack, (Text) content); + break; + case DocType: + printDocType(out, fstack, (DocType) content); + break; + default: + throw new IllegalStateException( + "Unexpected Content " + content.getCType()); + + } + } + } + + } + + + /** + * This will handle printing of any needed {@link Namespace} + * declarations. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * The current FormatStack + * @param ns + * Namespace to print definition of + * @throws XMLStreamException + * if the output fails + */ + protected void printNamespace(final XMLStreamWriter out, final FormatStack fstack, + final Namespace ns) throws XMLStreamException { + final String prefix = ns.getPrefix(); + final String uri = ns.getURI(); + + out.writeNamespace(prefix, uri); + } + + /** + * This will handle printing of an {@link Attribute}. + * + * @param out + * XMLStreamWriter to use. + * @param fstack + * The current FormatStack + * @param attribute + * Attribute to output + * @throws XMLStreamException + * if the output fails + */ + protected void printAttribute(final XMLStreamWriter out, final FormatStack fstack, + final Attribute attribute) throws XMLStreamException { + + if (!attribute.isSpecified() && fstack.isSpecifiedAttributesOnly()) { + return; + } + + final Namespace ns = attribute.getNamespace(); + if (ns == Namespace.NO_NAMESPACE) { + out.writeAttribute(attribute.getName(), attribute.getValue()); + } else { + out.writeAttribute(ns.getPrefix(), ns.getURI(), + attribute.getName(), attribute.getValue()); + } + } + +} diff --git a/core/src/java/org/jdom/output/support/AbstractXMLOutputProcessor.java b/core/src/java/org/jdom/output/support/AbstractXMLOutputProcessor.java new file mode 100644 index 0000000..9b7b68c --- /dev/null +++ b/core/src/java/org/jdom/output/support/AbstractXMLOutputProcessor.java @@ -0,0 +1,1019 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.xml.transform.Result; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.IllegalDataException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.Verifier; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.XMLOutputter2; +import org.jdom.util.NamespaceStack; + +/** + * This class provides a concrete implementation of {@link XMLOutputProcessor} + * for supporting the {@link XMLOutputter2}. + *

+ *

Overview

+ *

+ * This class is marked abstract even though all methods are fully implemented. + * The process*(...) methods are public because they match the + * XMLOutputProcessor interface but the remaining methods are all protected. + *

+ * People who want to create a custom XMLOutputProcessor for XMLOutputter are + * able to extend this class and modify any functionality they want. Before + * sub-classing this you should first check to see if the {@link Format} class + * can get you the results you want. + *

+ * Subclasses of this should have reentrant methods. This is + * easiest to accomplish simply by not allowing any instance fields. If your + * sub-class has an instance field/variable, then it's probably broken. + *

+ *

The Stacks

+ *

+ * One significant feature of this implementation is that it creates and + * maintains both a {@link NamespaceStack} and {@link FormatStack} that are + * managed in the + * {@link #printElement(Writer, FormatStack, NamespaceStack, Element)} method. + * The stacks are pushed and popped in that method only. They significantly + * improve the performance and readability of the code. + *

+ * The NamespaceStack is only sent through to the + * {@link #printElement(Writer, FormatStack, NamespaceStack, Element)} and + * {@link #printContent(Writer, FormatStack, NamespaceStack, Walker)} methods, + * but the FormatStack is pushed through to all print* Methods. + *

+ *

Text Processing

+ *

+ * In XML the concept of 'Text' can be loosely defined as anything that can be + * found between an Element's start and end tags, excluding Comments and + * Processing Instructions. When considered from a JDOM perspective, this means + * {@link Text}, {@link CDATA} and {@link EntityRef} content. This will be + * referred to as 'Text-like content' + *

+ * XMLOutputter delegates the management and formatting of Content to a + * Walker instance. See {@link Walker} and its various implementations for + * details on how the Element content is processed. + *

+ * Because the Walker interface specifies that Text/CDATA content may be + * returned as either Text/CDATA instances or as formatted String values + * this class sometimes uses printCDATA(...) and printText(...), and sometimes + * uses the more direct {@link #textCDATA(Writer, String)} or + * {@link #textRaw(Writer, String)} as + * appropriate. In other words, subclasses should probably override these second + * methods instead of the print methods. + *

+ *

Non-Text Content

+ *

+ * Non-text content is processed via the respective print* methods. The usage + * should be logical based on the method name. + *

+ * The general observations are: + *

    + *
  • printElement - maintains the Stacks, prints the element open tags, with + * attributes and namespaces. It checks to see whether the Element is text-only, + * or has non-text content. If it is text-only there is no indent/newline + * handling and it delegates to the correct text-type print method, otherwise it + * delegates to printContent. + *
  • printContent is called to output all lists of Content. It assumes that + * all whitespace indentation/newlines are appropriate before it is called, but + * it will ensure that padding is appropriate between the items in the list. + *
+ *

+ *

Final Notes

No methods actually write to the destination Writer + * except the write(...) methods. Thus, all other methods do their + * respective processing and delegate the actual destination output to the + * {@link #write(Writer, char)} or {@link #write(Writer, String)} methods. + *

+ * All Text-like content (printCDATA, printText, and printEntityRef) will + * ultimately be output through the the text* methods (and no other content). + *

+ * + * @see XMLOutputter2 + * @see XMLOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public abstract class AbstractXMLOutputProcessor extends AbstractOutputProcessor + implements XMLOutputProcessor { + + /** Simple constant for an open-CDATA */ + protected static final String CDATAPRE = ""; + + + + /* ******************************************* + * XMLOutputProcessor implementation. + * ******************************************* + */ + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.Document, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final Document doc) throws IOException { + printDocument(out, new FormatStack(format), new NamespaceStack(), doc); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.DocType, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final DocType doctype) throws IOException { + printDocType(out, new FormatStack(format), doctype); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.Element, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final Element element) throws IOException { + // If this is the root element we could pre-initialize the + // namespace stack with the namespaces + printElement(out, new FormatStack(format), new NamespaceStack(), + element); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * java.util.List, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final List list) + throws IOException { + FormatStack fstack = new FormatStack(format); + Walker walker = buildWalker(fstack, list, true); + printContent(out, fstack, new NamespaceStack(), walker); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.CDATA, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final CDATA cdata) throws IOException { + // we use the powers of the Walker to manage text-like content. + final List list = Collections.singletonList(cdata); + FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, true); + if (walker.hasNext()) { + printContent(out, fstack, new NamespaceStack(), walker); + } + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.Text, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final Text text) throws IOException { + // we use the powers of the Walker to manage text-like content. + final List list = Collections.singletonList(text); + FormatStack fstack = new FormatStack(format); + final Walker walker = buildWalker(fstack, list, true); + if (walker.hasNext()) { + printContent(out, fstack, new NamespaceStack(), walker); + } + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.Comment, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final Comment comment) throws IOException { + printComment(out, new FormatStack(format), comment); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.ProcessingInstruction, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final ProcessingInstruction pi) throws IOException { + FormatStack fstack = new FormatStack(format); + // Output PI verbatim, disregarding TrAX escaping PIs. + fstack.setIgnoreTrAXEscapingPIs(true); + printProcessingInstruction(out, fstack, pi); + out.flush(); + } + + /* + * (non-Javadoc) + * + * @see org.jdom.output.XMLOutputProcessor#process(java.io.Writer, + * org.jdom.EntityRef, org.jdom.output.Format) + */ + @Override + public void process(final Writer out, final Format format, + final EntityRef entity) throws IOException { + printEntityRef(out, new FormatStack(format), entity); + out.flush(); + } + + /* + * ======================================================================== + * Methods that actually write data to output. None of the other methods + * should directly write to the output unless they use these methods. + * ======================================================================== + */ + + /** + * Print some string value to the output. Null values are ignored. This + * ignore-null property is used for a few tricks. + * + * @param out + * The Writer to write to. + * @param str + * The String to write (can be null). + * @throws IOException + * if the out Writer fails. + */ + protected void write(final Writer out, final String str) throws IOException { + if (str == null) { + return; + } + out.write(str); + } + + /** + * Write a single character to the output Writer. + * + * @param out + * The Writer to write to. + * @param c + * The char to write. + * @throws IOException + * if the Writer fails. + */ + protected void write(final Writer out, final char c) throws IOException { + out.write(c); + } + + /* + * ======================================================================== + * Support methods for Text-content formatting. Should all be protected. The + * following are used when printing Text-based data. Because of complicated + * multi-sequential text sometimes the requirements are odd. All Text + * content will be output using these methods, which is why there is the None + * version. + * ======================================================================== + */ + + /** + * This will take the three pre-defined entities in XML 1.0 ('<', '>', + * and '&' - used specifically in XML elements) as well as CR/NL and + * Quote characters which require escaping inside Attribute values and + * convert their character representation to the appropriate entity + * reference suitable for XML attribute content. Further, some special + * characters (e.g. characters that are not valid in the current encoding) + * are converted to escaped representations. + *

+ * Note: If {@link FormatStack#getEscapeOutput()} is false then no + * escaping will happen. + * + * @param out + * The destination Writer + * @param fstack + * The {@link FormatStack} + * @param value + * String Attribute value to escape. + * @throws IOException + * if the destination Writer fails. + * @throws IllegalDataException + * if an entity can not be escaped + */ + protected void attributeEscapedEntitiesFilter(final Writer out, + final FormatStack fstack, final String value) throws IOException { + + if (!fstack.getEscapeOutput()) { + // no escaping... + write(out, value); + return; + } + + write(out, Format.escapeAttribute(fstack.getEscapeStrategy(), value)); + + } + + /** + * Convenience method that simply passes the input str to + * {@link #write(Writer, String)}. This could be useful for subclasses to + * hook in to. All text-type output will come through this or the + * {@link #textRaw(Writer, char)} method. + * + * @param out + * the destination writer. + * @param str + * the String to write. + * @throws IOException + * if the Writer fails. + */ + protected void textRaw(final Writer out, final String str) throws IOException { + write(out, str); + } + + /** + * Convenience method that simply passes the input char to + * {@link #write(Writer, char)}. This could be useful for subclasses to hook + * in to. All text-type output will come through this or the + * {@link #textRaw(Writer, String)} method. + * + * @param out + * the destination Writer. + * @param ch + * the char to write. + * @throws IOException + * if the Writer fails. + */ + protected void textRaw(final Writer out, final char ch) throws IOException { + write(out, ch); + } + + /** + * Write an {@link EntityRef} to the destination. + * + * @param out + * the destination Writer. + * @param name + * the EntityRef's name. + * @throws IOException + * if the Writer fails. + */ + protected void textEntityRef(final Writer out, final String name) throws IOException { + textRaw(out, '&'); + textRaw(out, name); + textRaw(out, ';'); + } + + /** + * Write a {@link CDATA} to the destination + * @param out the destination Writer + * @param text the CDATA text + * @throws IOException if the Writer fails. + */ + protected void textCDATA(final Writer out, final String text) throws IOException { + textRaw(out, CDATAPRE); + textRaw(out, text); + textRaw(out, CDATAPOST); + } + + /* ******************************************* + * Support methods for output. Should all be protected. All content-type + * print methods have a FormatStack. Only printContent is responsible for + * outputting appropriate indenting and newlines, which are easily available + * using the FormatStack.getLevelIndent() and FormatStack.getLevelEOL(). + * ******************************************* + */ + + /** + * This will handle printing of a {@link Document}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param doc + * Document to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printDocument(final Writer out, final FormatStack fstack, + final NamespaceStack nstack, final Document doc) throws IOException { + + + // If there is no root element then we cannot use the normal ways to + // access the ContentList because Document throws an exception. + // so we hack it and just access it by index. + List list = doc.hasRootElement() ? doc.getContent() : + new ArrayList(doc.getContentSize()); + if (list.isEmpty()) { + final int sz = doc.getContentSize(); + for (int i = 0; i < sz; i++) { + list.add(doc.getContent(i)); + } + } + + printDeclaration(out, fstack); + + Walker walker = buildWalker(fstack, list, true); + if (walker.hasNext()) { + while (walker.hasNext()) { + + final Content c = walker.next(); + // we do not ignore Text-like things in the Document. + // the walker creates the indenting for us. + if (c == null) { + // but, what we do is ensure it is all whitespace, and not CDATA + final String padding = walker.text(); + if (padding != null && Verifier.isAllXMLWhitespace(padding) && + !walker.isCDATA()) { + // we do not use the escaping or text* method because this + // content is outside of the root element, and thus is not + // strict text. + write(out, padding); + } + } else { + switch (c.getCType()) { + case Comment : + printComment(out, fstack, (Comment)c); + break; + case DocType : + printDocType(out, fstack, (DocType)c); + break; + case Element : + printElement(out, fstack, nstack, (Element)c); + break; + case ProcessingInstruction : + printProcessingInstruction(out, fstack, + (ProcessingInstruction)c); + break; + case Text : + final String padding = ((Text)c).getText(); + if (padding != null && Verifier.isAllXMLWhitespace(padding)) { + // we do not use the escaping or text* method because this + // content is outside of the root element, and thus is not + // strict text. + write(out, padding); + } + default : + // do nothing. + } + } + + } + + if (fstack.getLineSeparator() != null) { + write(out, fstack.getLineSeparator()); + } + } + + } + + /** + * This will handle printing of the XML declaration. Assumes XML version 1.0 + * since we don't directly know. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @throws IOException + * if the destination Writer fails + */ + protected void printDeclaration(final Writer out, final FormatStack fstack) throws IOException { + + // Only print the declaration if it's not being omitted + if (fstack.isOmitDeclaration()) { + return; + } + // Declaration is never indented. + // write(out, fstack.getLevelIndent()); + + // Assume 1.0 version + if (fstack.isOmitEncoding()) { + write(out, ""); + } else { + write(out, ""); + } + + // Print new line after decl always, even if no other new lines + // Helps the output look better and is semantically + // inconsequential + // newline(out, fstack); + write(out, fstack.getLineSeparator()); + } + + /** + * This will handle printing of a {@link DocType}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param docType + * DocType to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printDocType(final Writer out, final FormatStack fstack, + final DocType docType) throws IOException { + + final String publicID = docType.getPublicID(); + final String systemID = docType.getSystemID(); + final String internalSubset = docType.getInternalSubset(); + boolean hasPublic = false; + + // Declaration is never indented. + // write(out, fstack.getLevelIndent()); + + write(out, ""); + + } + + /** + * This will handle printing of a {@link ProcessingInstruction}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param pi + * ProcessingInstruction to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printProcessingInstruction(final Writer out, + final FormatStack fstack, final ProcessingInstruction pi) + throws IOException { + String target = pi.getTarget(); + boolean piProcessed = false; + + if (fstack.isIgnoreTrAXEscapingPIs() == false) { + if (target.equals(Result.PI_DISABLE_OUTPUT_ESCAPING)) { + // special case... change the FormatStack + fstack.setEscapeOutput(false); + piProcessed = true; + } + else if (target.equals(Result.PI_ENABLE_OUTPUT_ESCAPING)) { + // special case... change the FormatStack + fstack.setEscapeOutput(true); + piProcessed = true; + } + } + if (piProcessed == false) { + String rawData = pi.getData(); + + // Write or if no data then just + if (!"".equals(rawData)) { + write(out, ""); + } + else { + write(out, ""); + } + } + } + + /** + * This will handle printing of a {@link Comment}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param comment + * Comment to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printComment(final Writer out, final FormatStack fstack, + final Comment comment) throws IOException { + write(out, ""); + } + + /** + * This will handle printing of an {@link EntityRef}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param entity + * EntotyRef to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printEntityRef(final Writer out, final FormatStack fstack, + final EntityRef entity) throws IOException { + // EntityRefs are treated like text, not indented/newline content. + textEntityRef(out, entity.getName()); + } + + /** + * This will handle printing of a {@link CDATA}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param cdata + * CDATA to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printCDATA(final Writer out, final FormatStack fstack, + final CDATA cdata) throws IOException { + // CDATAs are treated like text, not indented/newline content. + textCDATA(out, cdata.getText()); + } + + /** + * This will handle printing of a {@link Text}. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param text + * Text to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printText(final Writer out, final FormatStack fstack, + final Text text) throws IOException { + if (fstack.getEscapeOutput()) { + textRaw(out, Format.escapeText(fstack.getEscapeStrategy(), + fstack.getLineSeparator(), text.getText())); + + return; + } + textRaw(out, text.getText()); + } + + /** + * This will handle printing of an {@link Element}. + *

+ * This method arranges for outputting the Element infrastructure including + * Namespace Declarations and Attributes. + * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param element + * Element to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printElement(final Writer out, final FormatStack fstack, + final NamespaceStack nstack, final Element element) throws IOException { + + nstack.push(element); + try { + final List content = element.getContent(); + + // Print the beginning of the tag plus attributes and any + // necessary namespace declarations + write(out, "<"); + + write(out, element.getQualifiedName()); + + // Print the element's namespace, if appropriate + for (final Namespace ns : nstack.addedForward()) { + printNamespace(out, fstack, ns); + } + + // Print out attributes + if (element.hasAttributes()) { + for (final Attribute attribute : element.getAttributes()) { + printAttribute(out, fstack, attribute); + } + } + + if (content.isEmpty()) { + // Case content is empty + if (fstack.isExpandEmptyElements()) { + write(out, ">"); + } + else { + write(out, " />"); + } + // nothing more to do. + return; + } + + // OK, we have real content to push. + fstack.push(); + try { + + // Check for xml:space and adjust format settings + final String space = element.getAttributeValue("space", + Namespace.XML_NAMESPACE); + + if ("default".equals(space)) { + fstack.setTextMode(fstack.getDefaultMode()); + } + else if ("preserve".equals(space)) { + fstack.setTextMode(TextMode.PRESERVE); + } + + // note we ensure the FStack is right before creating the walker + Walker walker = buildWalker(fstack, content, true); + + if (!walker.hasNext()) { + // the walker has formatted out whatever content we had + if (fstack.isExpandEmptyElements()) { + write(out, ">"); + } + else { + write(out, " />"); + } + // nothing more to do. + return; + } + // we have some content. + write(out, ">"); + if (!walker.isAllText()) { + // we need to newline/indent + textRaw(out, fstack.getPadBetween()); + } + + printContent(out, fstack, nstack, walker); + + if (!walker.isAllText()) { + // we need to newline/indent + textRaw(out, fstack.getPadLast()); + } + write(out, ""); + + } finally { + fstack.pop(); + } + } finally { + nstack.pop(); + } + + } + + /** + * This will handle printing of a List of {@link Content}. + *

+ * The list of Content is basically processed as one of three types of + * content + *

    + *
  1. Consecutive text-type (Text, CDATA, and EntityRef) content + *
  2. Stand-alone text-type content + *
  3. Non-text-type content. + *
+ * Although the code looks complex, the theory is conceptually simple: + *
    + *
  1. identify one of the three types (consecutive, stand-alone, non-text) + *
  2. do indent if any is specified. + *
  3. send the type to the respective print* handler (e.g. + * {@link #printCDATA(Writer, FormatStack, CDATA)}, or + * {@link #printComment(Writer, FormatStack, Comment)}, + *
  4. do a newline if one is specified. + *
  5. loop back to 1. until there's no more content to process. + *
+ * + * @param out + * Writer to use. + * @param fstack + * the FormatStack + * @param nstack + * the NamespaceStack + * @param walker + * {@link Walker} of Content to write. + * @throws IOException + * if the destination Writer fails + */ + protected void printContent(final Writer out, + final FormatStack fstack, final NamespaceStack nstack, + final Walker walker) + throws IOException { + + while (walker.hasNext()) { + Content c = walker.next(); + if (c == null) { + // it is a text value of some sort. + final String t = walker.text(); + if (walker.isCDATA()) { + textCDATA(out, t); + } else { + textRaw(out, t); + } + } else { + switch(c.getCType()) { + case CDATA: + printCDATA(out, fstack, (CDATA)c); + break; + case Comment: + printComment(out, fstack, (Comment)c); + break; + case DocType: + printDocType(out, fstack, (DocType)c); + break; + case Element: + printElement(out, fstack, nstack, (Element)c); + break; + case EntityRef: + printEntityRef(out, fstack, (EntityRef)c); + break; + case ProcessingInstruction: + printProcessingInstruction(out, fstack, + (ProcessingInstruction)c); + break; + case Text: + printText(out, fstack, (Text)c); + break; + } + } + } + + } + + /** + * This will handle printing of any needed {@link Namespace} + * declarations. + * + * @param out + * Writer to use. + * @param fstack + * The current FormatStack + * @param ns + * Namespace to print definition of + * @throws IOException + * if the output fails + */ + protected void printNamespace(final Writer out, final FormatStack fstack, + final Namespace ns) throws IOException { + final String prefix = ns.getPrefix(); + final String uri = ns.getURI(); + + write(out, " xmlns"); + if (!prefix.equals("")) { + write(out, ":"); + write(out, prefix); + } + write(out, "=\""); + attributeEscapedEntitiesFilter(out, fstack, uri); + write(out, "\""); + } + + /** + * This will handle printing of an {@link Attribute}. + * + * @param out + * Writer to use. + * @param fstack + * The current FormatStack + * @param attribute + * Attribute to output + * @throws IOException + * if the output fails + */ + protected void printAttribute(final Writer out, final FormatStack fstack, + final Attribute attribute) throws IOException { + + if (!attribute.isSpecified() && fstack.isSpecifiedAttributesOnly()) { + return; + } + write(out, " "); + write(out, attribute.getQualifiedName()); + write(out, "="); + + write(out, "\""); + attributeEscapedEntitiesFilter(out, fstack, attribute.getValue()); + write(out, "\""); + } + +} diff --git a/core/src/java/org/jdom/output/support/DOMOutputProcessor.java b/core/src/java/org/jdom/output/support/DOMOutputProcessor.java new file mode 100644 index 0000000..162216b --- /dev/null +++ b/core/src/java/org/jdom/output/support/DOMOutputProcessor.java @@ -0,0 +1,240 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.DOMOutputter; +import org.jdom.output.Format; + +/** + * This interface provides a base support for the {@link DOMOutputter}. + *

+ * People who want to create a custom DOMOutputProcessor for DOMOutputter are + * able to implement this interface with the following notes and restrictions: + *

    + *
  1. The DOMOutputter will call one, and only one of the + * process(Format,*) methods each time the DOMOutputter is + * requested to output some JDOM content. It is thus safe to assume that a + * process(Format,*) method can set up any infrastructure needed to + * process the content, and that the DOMOutputter will not re-call that method, + * or some other process(Format,*) method for the same output + * sequence. + *
  2. The process methods should be thread-safe and reentrant: The same + * process(Format,*) method may (will) be called concurrently from + * different threads. + *
+ *

+ * The {@link AbstractDOMOutputProcessor} class is a full implementation of this + * interface and is fully customisable. People who want a custom DOMOutputter + * are encouraged to extend the AbstractDOMOutputProcessor rather than do a full + * re-implementation of this interface. + * + * @see DOMOutputter + * @see AbstractDOMOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public interface DOMOutputProcessor { + + /** + * This will convert the {@link Document} to the given DOM + * Document. + *

+ * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param doc + * Document to format. + * @return The same DOM Document as the input document, but with the JDOM + * content converted and added. + */ + public org.w3c.dom.Document process(org.w3c.dom.Document basedoc, + Format format, Document doc); + + /** + * This will convert the {@link Element} using the given DOM + * Document to create the resulting DOM Element. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param element + * Element to format. + * @return The input JDOM Element converted to a DOM Element + */ + public org.w3c.dom.Element process(org.w3c.dom.Document basedoc, + Format format, Element element); + + /** + * This will convert the list of JDOM {@link Content} using the + * given DOM Document to create the resulting list of DOM Nodes. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param list + * JDOM Content to convert. + * @return The input JDOM Content List converted to a List of DOM Nodes + */ + public List process(org.w3c.dom.Document basedoc, + Format format, List list); + + /** + * This will convert the {@link CDATA} using the given DOM + * Document to create the resulting DOM CDATASection. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param cdata + * CDATA to format. + * @return The input JDOM CDATA converted to a DOM CDATASection + */ + public org.w3c.dom.CDATASection process(org.w3c.dom.Document basedoc, + Format format, CDATA cdata); + + /** + * This will convert the {@link Text} using the given DOM + * Document to create the resulting DOM Text. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param text + * Text to format. + * @return The input JDOM Text converted to a DOM Text + */ + public org.w3c.dom.Text process(org.w3c.dom.Document basedoc, + Format format, Text text); + + /** + * This will convert the {@link Comment} using the given DOM + * Document to create the resulting DOM Comment. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param comment + * Comment to format. + * @return The input JDOM Comment converted to a DOM Comment + */ + public org.w3c.dom.Comment process(org.w3c.dom.Document basedoc, + Format format, Comment comment); + + /** + * This will convert the {@link ProcessingInstruction} using + * the given DOM Document to create the resulting DOM ProcessingInstruction. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param pi + * ProcessingInstruction to format. + * @return The input JDOM ProcessingInstruction converted to a DOM + * ProcessingInstruction + */ + public org.w3c.dom.ProcessingInstruction process( + org.w3c.dom.Document basedoc, Format format, + ProcessingInstruction pi); + + /** + * This will convert the {@link EntityRef} using the given DOM + * Document to create the resulting DOM EntityReference. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param entity + * EntityRef to format. + * @return The input JDOM EntityRef converted to a DOM EntityReference + */ + public org.w3c.dom.EntityReference process(org.w3c.dom.Document basedoc, + Format format, EntityRef entity); + + /** + * This will convert the {@link Attribute} using the given DOM + * Document to create the resulting DOM Attr. + * + * @param basedoc + * The DOM document to use for the conversion + * @param format + * Format instance specifying output style + * @param attribute + * Attribute to format. + * @return The input JDOM Attribute converted to a DOM Attr + */ + public org.w3c.dom.Attr process(org.w3c.dom.Document basedoc, + Format format, Attribute attribute); + +} diff --git a/core/src/java/org/jdom/output/support/FormatStack.java b/core/src/java/org/jdom/output/support/FormatStack.java new file mode 100644 index 0000000..843e389 --- /dev/null +++ b/core/src/java/org/jdom/output/support/FormatStack.java @@ -0,0 +1,475 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import org.jdom.internal.ArrayCopy; +import org.jdom.output.EscapeStrategy; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; + +/** + * FormatStack implements a mechanism where the formatting details can be + * changed mid-tree, but then get reverted when that tree segment is + * complete. + *

+ * This class is intended as a working-class for in the various outputter + * implementations. It is inly public so that people extending the + * Abstract*Processor classes can take advantage of it's functionality. + *

+ * The value this class adds is: + *

    + *
  • Fast - + *
+ * + * @since JDOM2 + * @author Rolf Lear + */ +public final class FormatStack { + + private int capacity = 16; // can grow if more than 16 levels in XML + private int depth = 0; // current level in XML + + /* + * ==================================================================== + * The following values cannot be changed mid-way through the output + * ==================================================================== + */ + + private final TextMode defaultMode; // the base/initial Text mode + + /** The default indent is no spaces (as original document) */ + private final String indent; + + /** The encoding format */ + private final String encoding; + + /** New line separator */ + private final String lineSeparator; + + /** + * Whether or not to output the XML declaration - default is + * false + */ + private final boolean omitDeclaration; + + /** + * Whether or not to output the encoding in the XML declaration - + * default is false + */ + private final boolean omitEncoding; + + /** + * Whether or not to expand empty elements to + * <tagName></tagName> - default is false + */ + private final boolean expandEmptyElements; + + /** + * Whether or not to output 'specified' Attributes only + */ + private final boolean specifiedAttributesOnly; + + /** entity escape logic */ + private final EscapeStrategy escapeStrategy; + + /* + * ==================================================================== + * The following values can be changed mid-way through the output, hence + * they are arrays. + * ==================================================================== + */ + + /** The 'current' accumulated indent */ + private String[] levelIndent = new String[capacity]; + + /** The 'current' End-Of-Line */ + private String[] levelEOL = new String[capacity]; + + /** The padding to put between content items */ + private String[] levelEOLIndent = new String[capacity]; + + /** The padding to put after the last item (typically one less indent) */ + private String[] termEOLIndent = new String[capacity]; + + /** + * Whether TrAX output escaping disabling/enabling PIs are ignored or + * processed - default is false + */ + private boolean[] ignoreTrAXEscapingPIs = new boolean[capacity]; + + /** text handling mode */ + private TextMode[] mode = new TextMode[capacity]; + + /** escape Output logic - can be changed by */ + private boolean[] escapeOutput = new boolean[capacity]; + + /** + * Creates a new FormatStack seeded with the specified Format + * + * @param format + * the Format instance to seed the stack with. + */ + public FormatStack(Format format) { + indent = format.getIndent(); + lineSeparator = format.getLineSeparator(); + + encoding = format.getEncoding(); + omitDeclaration = format.getOmitDeclaration(); + omitEncoding = format.getOmitEncoding(); + expandEmptyElements = format.getExpandEmptyElements(); + escapeStrategy = format.getEscapeStrategy(); + defaultMode = format.getTextMode(); + specifiedAttributesOnly = format.isSpecifiedAttributesOnly(); + + mode[depth] = format.getTextMode(); + if (mode[depth] == TextMode.PRESERVE) { + // undo any special indenting and end-of-line management: + levelIndent[depth] = null; + levelEOL[depth] = null; + levelEOLIndent[depth] = null; + termEOLIndent[depth] = null; + } else { + levelIndent[depth] = format.getIndent() == null + ? null : ""; + levelEOL[depth] = format.getLineSeparator(); + levelEOLIndent[depth] = levelIndent[depth] == null ? + null : levelEOL[depth]; + termEOLIndent[depth] = levelEOLIndent[depth]; + + } + ignoreTrAXEscapingPIs[depth] = format.getIgnoreTrAXEscapingPIs(); + escapeOutput[depth] = true; + } + + /** + * If the indent strategy changes part way through a stack, we need to + * clear the previously calculated reusable 'lower' levels of the stack. + */ + private final void resetReusableIndents() { + int d = depth + 1; + while (d < levelIndent.length && levelIndent[d] != null) { + // all subsequent forays in to lower levels will need to be redone + levelIndent[d] = null; + d++; + } + } + + /** + * @return the original {@link Format#getIndent()}, may be null + */ + public String getIndent() { + return indent; + } + + /** + * @return the original {@link Format#getLineSeparator()} + */ + public String getLineSeparator() { + return lineSeparator; + } + + /** + * @return the original {@link Format#getEncoding()} + */ + public String getEncoding() { + return encoding; + } + + /** + * @return the original {@link Format#getOmitDeclaration()} + */ + public boolean isOmitDeclaration() { + return omitDeclaration; + } + + /** + * Indicate whether only those Attributes specified in the XML + * should be output. + * @return true if only the specified Attributes should be output, + * false if those Attributes defaulted from the DTD or XML schema + * should be output too. + */ + public boolean isSpecifiedAttributesOnly() { + return specifiedAttributesOnly; + } + + /** + * @return the original {@link Format#getOmitEncoding()} + */ + public boolean isOmitEncoding() { + return omitEncoding; + } + + /** + * @return the original {@link Format#getExpandEmptyElements()} + */ + public boolean isExpandEmptyElements() { + return expandEmptyElements; + } + + /** + * @return the original {@link Format#getEscapeStrategy()} + */ + public EscapeStrategy getEscapeStrategy() { + return escapeStrategy; + } + + /** + * @return the current depth's {@link Format#getIgnoreTrAXEscapingPIs()} + */ + public boolean isIgnoreTrAXEscapingPIs() { + return ignoreTrAXEscapingPIs[depth]; + } + + /** + * Set the current depth's {@link Format#getIgnoreTrAXEscapingPIs()} + * + * @param ignoreTrAXEscapingPIs + * the boolean value to set. + */ + public void setIgnoreTrAXEscapingPIs(boolean ignoreTrAXEscapingPIs) { + this.ignoreTrAXEscapingPIs[depth] = ignoreTrAXEscapingPIs; + } + + /** + * The escapeOutput flag can be set or unset. When set, Element text and + * Attribute values are 'escaped' so that the output is valid XML. When + * unset, the Element text and Attribute values are not escaped. + * + * @return the current depth's escapeOutput flag. + */ + public boolean getEscapeOutput() { + return escapeOutput[depth]; + } + + /** + * The escapeOutput flag can be set or unset. When set, Element text and + * Attribute values are 'escaped' so that the output is valid XML. When + * unset, the Element text and Attribute values are not escaped. + * + * @param escape + * what to set the current level's escapeOutput flag to. + */ + public void setEscapeOutput(boolean escape) { + escapeOutput[depth] = escape; + } + + /** + * @return the TextMode that was originally set for this stack before + * any modifications. + */ + public TextMode getDefaultMode() { + return defaultMode; + } + + /** + * @return the current depth's accumulated/maintained indent, may be null + */ + public String getLevelIndent() { + return levelIndent[depth]; + } + + /** + * Get the end-of-line indenting sequence for before the first item in an + * Element, as well as between subsequent items (but not after the last item) + * @return the String EOL sequence followed by an indent. Null if it should + * be ignored + */ + public String getPadBetween() { + return levelEOLIndent[depth]; + } + + /** + * Get the end-of-line indenting sequence for after the last item in an + * Element + * @return the String EOL sequence followed by an indent. Null if it should + * be ignored + */ + public String getPadLast() { + return termEOLIndent[depth]; + } + + /** + * Override the current depth's accumulated line indent. + * + * @param indent + * the indent to set. + */ + public void setLevelIndent(String indent) { + this.levelIndent[depth] = indent; + levelEOLIndent[depth] = (indent == null || levelEOL[depth] == null) ? + null : (levelEOL[depth] + indent); + resetReusableIndents(); + } + + /** + * @return the current depth's End-Of-Line sequence, may be null + */ + public String getLevelEOL() { + return levelEOL[depth]; + } + + /** + * Set the current depth's End-Of-Line sequence + * + * @param newline + * the new End-Of-Line sequence to set. + */ + public void setLevelEOL(String newline) { + this.levelEOL[depth] = newline; + resetReusableIndents(); + } + + /** + * @return the current depth's {@link Format#getTextMode()} + */ + public TextMode getTextMode() { + return mode[depth]; + } + + /** + * Change the current level's TextMode + * + * @param mode + * the new mode to set. + */ + public void setTextMode(TextMode mode) { + if (this.mode[depth] == mode) { + return; + } + this.mode[depth] = mode; + switch (mode) { + case PRESERVE: + levelEOL[depth] = null; + levelIndent[depth] = null; + levelEOLIndent[depth] = null; + termEOLIndent[depth] = null; + break; + default: + levelEOL[depth] = lineSeparator; + if (indent == null || lineSeparator == null) { + levelEOLIndent[depth] = null; + termEOLIndent[depth] = null; + } else { + if (depth > 0) { + final StringBuilder sb = new StringBuilder(indent.length() * depth); + for (int i = 1; i < depth; i++) { + sb.append(indent); + } + // the start point was '1', so we are one indent + // short, which is just right for the term.... + termEOLIndent[depth] = lineSeparator + sb.toString(); + // but we increase it once for the actual indent. + sb.append(indent); + levelIndent[depth] = sb.toString(); + } else { + termEOLIndent[depth] = lineSeparator; + levelIndent[depth] = ""; + } + levelEOLIndent[depth] = lineSeparator + levelIndent[depth]; + } + } + resetReusableIndents(); + } + + /** + * Create a new depth level on the stack. The previous level's details + * are copied to this level, and the accumulated indent (if any) is + * indented further. + */ + public void push() { + final int prev = depth++; + if (depth >= capacity) { + capacity *= 2; + levelIndent = ArrayCopy.copyOf(levelIndent, capacity); + levelEOL = ArrayCopy.copyOf(levelEOL, capacity); + levelEOLIndent = ArrayCopy.copyOf(levelEOLIndent, capacity); + termEOLIndent = ArrayCopy.copyOf(termEOLIndent, capacity); + ignoreTrAXEscapingPIs = ArrayCopy.copyOf(ignoreTrAXEscapingPIs, capacity); + mode = ArrayCopy.copyOf(mode, capacity); + escapeOutput = ArrayCopy.copyOf(escapeOutput, capacity); + } + + ignoreTrAXEscapingPIs[depth] = ignoreTrAXEscapingPIs[prev]; + mode[depth] = mode[prev]; + escapeOutput[depth] = escapeOutput[prev]; + + if (levelIndent[prev] == null || levelEOL[prev] == null) { + levelIndent[depth] = null; + levelEOL[depth] = null; + levelEOLIndent[depth] = null; + termEOLIndent[depth] = null; + } else if (levelIndent[depth] == null) { + // we need to build our level details .... + // cannot reuse previous ones. + levelEOL[depth] = levelEOL[prev]; + termEOLIndent[depth] = levelEOL[depth] + levelIndent[prev]; + levelIndent[depth] = levelIndent[prev] + indent; + levelEOLIndent[depth] = levelEOL[depth] + levelIndent[depth]; + } + } + + /** + * Move back a level on the stack. + */ + public void pop() { + // no need to clear previously used members in the stack. + // the stack is short-lived, and does not create new instances for + // the depth levels, in other words, it does not affect GC and does + // not save memory to clear the stack. + depth--; + } + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/output/support/SAXOutputProcessor.java b/core/src/java/org/jdom/output/support/SAXOutputProcessor.java new file mode 100644 index 0000000..d636201 --- /dev/null +++ b/core/src/java/org/jdom/output/support/SAXOutputProcessor.java @@ -0,0 +1,309 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.SAXOutputter; + +/** + * This interface provides a base support for the {@link SAXOutputter}. + *

+ * People who want to create a custom SAXOutputProcessor for SAXOutputter are + * able to implement this interface with the following notes and restrictions: + *

    + *
  1. The SAXOutputter will call one, and only one of the + * process(SAXTarget,Format,*) methods each time the SAXOutputter + * is requested to output some JDOM content. It is thus safe to assume that a + * process(SAXTarget,Format,*) method can set up any infrastructure + * needed to process the content, and that the SAXOutputter will not re-call + * that method, or some other process(SAXTarget,Format,*) method + * for the same output sequence. + *
  2. The process methods should be thread-safe and reentrant: The same + * process(SAXTarget,Format,*) method may (will) be called + * concurrently from different threads. + *
+ *

+ * The {@link AbstractSAXOutputProcessor} class is a full implementation of this + * interface and is fully customisable. People who want a custom SAXOutputter + * are encouraged to extend the AbstractSAXOutputProcessor rather than do a full + * re-implementation of this interface. + * + * @see SAXOutputter + * @see AbstractSAXOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public interface SAXOutputProcessor { + + /** + * This will print the {@link Document} to the given SAXTarget. + *

+ * Warning: using your own SAXTarget may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param doc + * Document to format. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, Document doc) + throws JDOMException; + + /** + * Print out the {@link DocType}. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param doctype + * DocType to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, DocType doctype) + throws JDOMException; + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param element + * Element to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, Element element) + throws JDOMException; + + /** + * Print out an {@link Element} encapsulated in start/end + * Document SAX events, including its {@link Attribute}s, and + * all contained (child) elements, etc. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param element + * Element to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public void processAsDocument(SAXTarget out, Format format, Element element) + throws JDOMException; + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param list + * List of nodes. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input list is null or contains null members + * @throws ClassCastException + * if any of the list members are not {@link Content} + */ + public abstract void process(SAXTarget out, Format format, + List list) throws JDOMException; + + /** + * This will handle printing out a list of nodes thats encapsulated in + * start/end Document SAX events. This can be useful for printing the + * content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param list + * List of nodes. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input list is null or contains null members + * @throws ClassCastException + * if any of the list members are not {@link Content} + */ + public abstract void processAsDocument(SAXTarget out, Format format, + List list) throws JDOMException; + + /** + * Print out a {@link CDATA} node. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param cdata + * CDATA to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, CDATA cdata) + throws JDOMException; + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param text + * Text to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, Text text) + throws JDOMException; + + /** + * Print out a {@link Comment}. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param comment + * Comment to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, Comment comment) + throws JDOMException; + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param pi + * ProcessingInstruction to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, + ProcessingInstruction pi) throws JDOMException; + + /** + * Print out a {@link EntityRef}. + * + * @param out + * SAXTarget to use. + * @param format + * Format instance specifying output style + * @param entity + * EntityRef to output. + * @throws JDOMException + * if there is an issue encountered during output. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(SAXTarget out, Format format, EntityRef entity) + throws JDOMException; + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/output/support/SAXTarget.java b/core/src/java/org/jdom/output/support/SAXTarget.java new file mode 100644 index 0000000..a36aa34 --- /dev/null +++ b/core/src/java/org/jdom/output/support/SAXTarget.java @@ -0,0 +1,269 @@ +/*-- + + Copyright (C) 2011 - 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.LexicalHandler; + +import org.jdom.output.JDOMLocator; + +/** + * The target for all SAX notifications in this OuputProcessor + * + * @author Rolf Lear + */ +public final class SAXTarget { + + /** + * A locator specific to the SAXOutputter process. + * @author Rolf Lear + * + */ + public static final class SAXLocator implements JDOMLocator { + + private final String publicid, systemid; + private Object node = null ; + + /** + * Creates a SAXLocator which implements JDOMLocator + * @param publicid This Locator's SystemID + * @param systemid This Locator's PublicID + */ + public SAXLocator(String publicid, String systemid) { + super(); + this.publicid = publicid; + this.systemid = systemid; + } + + @Override + public int getColumnNumber() { + return -1; + } + + @Override + public int getLineNumber() { + return -1; + } + + @Override + public String getPublicId() { + return publicid; + } + + @Override + public String getSystemId() { + return systemid; + } + + @Override + public Object getNode() { + return node; + } + + /** + * Set the location on this SAXLocator + * @param node The location to set. + */ + public void setNode(Object node) { + this.node = node; + } + + } + + /** registered ContentHandler */ + private final ContentHandler contentHandler; + + /** registered ErrorHandler */ + private final ErrorHandler errorHandler; + + /** registered DTDHandler */ + private final DTDHandler dtdHandler; + + /** registered EntityResolver */ + private final EntityResolver entityResolver; + + /** registered LexicalHandler */ + private final LexicalHandler lexicalHandler; + + /** registered DeclHandler */ + private final DeclHandler declHandler; + + private final SAXLocator locator; + + /** + * Whether to report attribute namespace declarations as xmlns + * attributes. Defaults to false as per SAX specifications. + * + * @see SAX + * namespace specifications + */ + private final boolean declareNamespaces; + + /** + * Whether to report DTD events to DeclHandlers and LexicalHandlers. + * Defaults to true. + */ + private final boolean reportDtdEvents; + + /** + * Create the collection of handlers for a SAXOutputProcessor + * + * @param contentHandler + * The ContentHandler + * @param errorHandler + * The ErrorHandler + * @param dtdHandler + * The DTDHandler + * @param entityResolver + * The EntityResolver + * @param lexicalHandler + * The LexicalHandler + * @param declHandler + * The DeclHandler + * @param declareNamespaces + * Whether to declare Namespaces + * @param reportDtdEvents + * Whether to report DTD Events + * @param publicID + * The public ID (null if none) + * @param systemID + * The System ID (null if none) + */ + public SAXTarget(ContentHandler contentHandler, + ErrorHandler errorHandler, DTDHandler dtdHandler, + EntityResolver entityResolver, LexicalHandler lexicalHandler, + DeclHandler declHandler, boolean declareNamespaces, + boolean reportDtdEvents, String publicID, String systemID) { + super(); + this.contentHandler = contentHandler; + this.errorHandler = errorHandler; + this.dtdHandler = dtdHandler; + this.entityResolver = entityResolver; + this.lexicalHandler = lexicalHandler; + this.declHandler = declHandler; + this.declareNamespaces = declareNamespaces; + this.reportDtdEvents = reportDtdEvents; + this.locator = new SAXLocator(publicID, systemID); + + } + + /** + * @return The target ContentHandler + */ + public ContentHandler getContentHandler() { + return contentHandler; + } + + /** + * @return The target ErrorHandler + */ + public ErrorHandler getErrorHandler() { + return errorHandler; + } + + /** + * @return The target DTDHandler + */ + public DTDHandler getDTDHandler() { + return dtdHandler; + } + + /** + * @return The target EntityResolver + */ + public EntityResolver getEntityResolver() { + return entityResolver; + } + + /** + * @return The target LexicalHandler + */ + public LexicalHandler getLexicalHandler() { + return lexicalHandler; + } + + /** + * @return The target DeclHandler + */ + public DeclHandler getDeclHandler() { + return declHandler; + } + + /** + * @return Whether to declare Namespaces + */ + public boolean isDeclareNamespaces() { + return declareNamespaces; + } + + /** + * @return Whether to report DTD Events + */ + public boolean isReportDTDEvents() { + return reportDtdEvents; + } + + /** + * @return the Locator used for this Output + */ + public SAXLocator getLocator() { + return locator; + } + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/output/support/StAXEventProcessor.java b/core/src/java/org/jdom/output/support/StAXEventProcessor.java new file mode 100644 index 0000000..776ee44 --- /dev/null +++ b/core/src/java/org/jdom/output/support/StAXEventProcessor.java @@ -0,0 +1,279 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.util.XMLEventConsumer; +import javax.xml.stream.XMLStreamException; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; + +/** + * This interface provides a base support for the {@link XMLOutputter2}. + *

+ * People who want to create a custom XMLOutputProcessor for XMLOutputter are + * able to implement this interface with the following notes and restrictions: + *

    + *
  1. The XMLOutputter will call one, and only one of the process(XMLEventConsumer,Format,*) methods each + * time the XMLOutputter is requested to output some JDOM content. It is thus + * safe to assume that a process(XMLEventConsumer,Format,*) method can set up any + * infrastructure needed to process the content, and that the XMLOutputter will + * not re-call that method, or some other process(XMLEventConsumer,Format,*) method for the same output + * sequence. + *
  2. The process methods should be thread-safe and reentrant: The same + * process(XMLEventConsumer,Format,*) method may (will) be called concurrently from different threads. + *
+ *

+ * The {@link AbstractXMLOutputProcessor} class is a full implementation of this + * interface and is fully customisable. People who want a custom XMLOutputter + * are encouraged to extend the AbstractXMLOutputProcessor rather than do a full + * re-implementation of this interface. + * + * @see XMLOutputter2 + * @see AbstractXMLOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public interface StAXEventProcessor { + + /** + * This will print the {@link Document} to the given XMLEventConsumer. + *

+ * Warning: using your own XMLEventConsumer may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param doc + * Document to format. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, Document doc) throws XMLStreamException; + + /** + * Print out the {@link DocType}. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param doctype + * DocType to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, DocType doctype) throws XMLStreamException; + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param element + * Element to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, Element element) throws XMLStreamException; + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param list + * List of nodes. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input list is null or contains null members + * @throws ClassCastException + * if any of the list members are not {@link Content} + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, List list) + throws XMLStreamException; + + /** + * Print out a {@link CDATA} node. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactpry + * XMLEventFactory for creating XMLEvent instances. + * @param cdata + * CDATA to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactpry, CDATA cdata) throws XMLStreamException; + + /** + * Print out a {@link Text} node. Performs the necessary entity + * escaping and whitespace stripping. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param text + * Text to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, Text text) throws XMLStreamException; + + /** + * Print out a {@link Comment}. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param comment + * Comment to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, Comment comment) throws XMLStreamException; + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param pi + * ProcessingInstruction to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, ProcessingInstruction pi) + throws XMLStreamException; + + /** + * Print out a {@link EntityRef}. + * + * @param out + * XMLEventConsumer to use. + * @param format + * Format instance specifying output style + * @param eventfactory + * XMLEventFactory for creating XMLEvent instances. + * @param entity + * EntityRef to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLEventConsumer out, Format format, XMLEventFactory eventfactory, EntityRef entity) throws XMLStreamException; + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/output/support/StAXStreamProcessor.java b/core/src/java/org/jdom/output/support/StAXStreamProcessor.java new file mode 100644 index 0000000..f4bfec5 --- /dev/null +++ b/core/src/java/org/jdom/output/support/StAXStreamProcessor.java @@ -0,0 +1,260 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; + +/** + * This interface provides a base support for the {@link XMLOutputter2}. + *

+ * People who want to create a custom XMLOutputProcessor for XMLOutputter are + * able to implement this interface with the following notes and restrictions: + *

    + *
  1. The XMLOutputter will call one, and only one of the process(XMLStreamWriter,Format,*) methods each + * time the XMLOutputter is requested to output some JDOM content. It is thus + * safe to assume that a process(XMLStreamWriter,Format,*) method can set up any + * infrastructure needed to process the content, and that the XMLOutputter will + * not re-call that method, or some other process(XMLStreamWriter,Format,*) method for the same output + * sequence. + *
  2. The process methods should be thread-safe and reentrant: The same + * process(XMLStreamWriter,Format,*) method may (will) be called concurrently from different threads. + *
+ *

+ * The {@link AbstractXMLOutputProcessor} class is a full implementation of this + * interface and is fully customisable. People who want a custom XMLOutputter + * are encouraged to extend the AbstractXMLOutputProcessor rather than do a full + * re-implementation of this interface. + * + * @see XMLOutputter2 + * @see AbstractXMLOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public interface StAXStreamProcessor { + + /** + * This will print the {@link Document} to the given XMLStreamWriter. + *

+ * Warning: using your own XMLStreamWriter may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param doc + * Document to format. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, Document doc) throws XMLStreamException; + + /** + * Print out the {@link DocType}. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param doctype + * DocType to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, DocType doctype) throws XMLStreamException; + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param element + * Element to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, Element element) throws XMLStreamException; + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param list + * List of nodes. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input list is null or contains null members + * @throws ClassCastException + * if any of the list members are not {@link Content} + */ + public abstract void process(XMLStreamWriter out, Format format, List list) + throws XMLStreamException; + + /** + * Print out a {@link CDATA} node. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param cdata + * CDATA to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, CDATA cdata) throws XMLStreamException; + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param text + * Text to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, Text text) throws XMLStreamException; + + /** + * Print out a {@link Comment}. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param comment + * Comment to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, Comment comment) throws XMLStreamException; + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param pi + * ProcessingInstruction to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, ProcessingInstruction pi) + throws XMLStreamException; + + /** + * Print out a {@link EntityRef}. + * + * @param out + * XMLStreamWriter to use. + * @param format + * Format instance specifying output style + * @param entity + * EntityRef to output. + * @throws XMLStreamException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(XMLStreamWriter out, Format format, EntityRef entity) throws XMLStreamException; + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/output/support/Walker.java b/core/src/java/org/jdom/output/support/Walker.java new file mode 100644 index 0000000..1bf917d --- /dev/null +++ b/core/src/java/org/jdom/output/support/Walker.java @@ -0,0 +1,145 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.NoSuchElementException; + +import org.jdom.CDATA; +import org.jdom.Content; +import org.jdom.EntityRef; +import org.jdom.Text; + +/** + * A model for walking the (potentially formatted) content of an Element. + *

+ * Implementations of this class restructure the content to a particular format + * and expose the restructured content in this 'Walker' which is a loose + * equivalent to an iterator. + *

+ * The next() method will return a Content instance (perhaps null) if there + * is more content. If the returned content is null, then there will be some + * formatted characters available in the text() method. These characters may + * need to be represented as CDATA (check the isCDATA() method). + *

+ * Not all CDATA and Text nodes need to be reformatted, and as a result they + * may be returned as their original CDATA or Text instances instead of using + * the formatted text() / isCDATA() mechanism. + *

+ * The 'Rules' for the walkers are that no padding is done before the + * first content step, and no padding is done after the last content step (but + * the first/last content items may be trimmed to the correct format). + * Any required padding will be done in plain text (not CDATA) content. + * Consecutive CDATA sections may be separated by whitespace text for example. + * + * @author Rolf Lear + * + */ +public interface Walker { + + /** + * If all the content in this walker is empty, or if whatever content + * is available is Text-like. + *

+ * Text-like content is considered to be {@link Text}, {@link CDATA}, + * {@link EntityRef}, or any (potentially mixed) sequence of these types, + * but no other types. + * + * @return true if there is no content, or all content is Text + */ + public abstract boolean isAllText(); + + /** + * If all the content is Text-like ({@link #isAllText()} returns true), and + * additionally that any content is either Text or CDATA, and that the + * values of these Text/CDATA members are all XML Whitespace. + * @return true + */ + public abstract boolean isAllWhitespace(); + + /** + * Behaves similarly to to a regular Iterator + * + * @return true if there is more content to be processed + */ + public abstract boolean hasNext(); + + /** + * Similar to an Iterator, but null return values need special treatment. + * + * @return the next content to be processed, perhaps null if the next + * content is re-formatted text of some sort (Text / CDATA). + * @throws NoSuchElementException if there is no further content. + */ + public abstract Content next(); + + /** + * If the previous call to next() returned null, then this will return the + * required text to be processed. Check to see whether this text is CDATA + * by calling the isCDATA() method. + * @return The current text value (null if the previous invocation of next() + * returned a non-null value). + * @throws IllegalStateException if there was not previous call to next() + */ + public abstract String text(); + + /** + * If the previous next() method returned null, then this will indicate + * whether the current text() value is CDATA or regular Text. + * @return true if the current text() is valid, and is CDATA. + * @throws IllegalStateException if there was not previous call to next() + */ + public abstract boolean isCDATA(); + +} diff --git a/core/src/java/org/jdom/output/support/WalkerNORMALIZE.java b/core/src/java/org/jdom/output/support/WalkerNORMALIZE.java new file mode 100644 index 0000000..c2aae3e --- /dev/null +++ b/core/src/java/org/jdom/output/support/WalkerNORMALIZE.java @@ -0,0 +1,152 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import org.jdom.Content; +import org.jdom.Verifier; + +/** + * This Walker implementation will produce trimmed text content. + * + * @author Rolf Lear + * + */ +public class WalkerNORMALIZE extends AbstractFormattedWalker { + + /** + * Create the Trimmed walker instance. + * @param content The list of content to format + * @param fstack The current stack. + * @param escape Whether Text values should be escaped. + */ + public WalkerNORMALIZE(final List content, + final FormatStack fstack, final boolean escape) { + super(content, fstack, escape); + } + + private boolean isSpaceFirst(String text) { + if (text.length() > 0) { + return Verifier.isXMLWhitespace(text.charAt(0)); + } + return false; + } + + private boolean isSpaceLast(String text) { + final int tlen = text.length(); + if (tlen > 0 && Verifier.isXMLWhitespace(text.charAt(tlen - 1))) { + return true; + } + return false; + } + + @Override + protected void analyzeMultiText(final MultiText mtext, + final int offset, final int len) { + boolean needspace = false; + boolean between = false; + + String ttext = null; + for (int i = 0; i < len; i++) { + final Content c = get(offset + i); + switch (c.getCType()) { + case Text : + ttext = c.getValue(); + if (Verifier.isAllXMLWhitespace(ttext)) { + if (between && ttext.length() > 0) { + needspace = true; + } + } else { + if (between && (needspace || isSpaceFirst(ttext))) { + mtext.appendText(Trim.NONE, " "); + } + mtext.appendText(Trim.COMPACT, ttext); + between = true; + needspace = isSpaceLast(ttext); + } + break; + case CDATA : + ttext = c.getValue(); + if (Verifier.isAllXMLWhitespace(ttext)) { + if (between && ttext.length() > 0) { + needspace = true; + } + } else { + if (between && (needspace || isSpaceFirst(ttext))) { + mtext.appendText(Trim.NONE, " "); + } + mtext.appendCDATA(Trim.COMPACT, ttext); + between = true; + needspace = isSpaceLast(ttext); + } + break; + case EntityRef: + // treat like any other content. + // raw. + default: + ttext = null; + if (between && needspace) { + mtext.appendText(Trim.NONE, " "); + } + mtext.appendRaw(c); + between = true; + needspace = false; + break; + } + } + } + +} diff --git a/core/src/java/org/jdom/output/support/WalkerPRESERVE.java b/core/src/java/org/jdom/output/support/WalkerPRESERVE.java new file mode 100644 index 0000000..c4beaf8 --- /dev/null +++ b/core/src/java/org/jdom/output/support/WalkerPRESERVE.java @@ -0,0 +1,154 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.jdom.Content; + +/** + * This Walker implementation walks a list of Content in it's original RAW + * format. There is no text manipulation, and all content will be returned as + * the input type. In other words, next() will never be null, and text() will + * always be null. + * + * @author Rolf Lear + * + */ +public class WalkerPRESERVE implements Walker { + + private static final Iterator EMPTYIT = new Iterator() { + @Override + public boolean hasNext() { + return false; + } + + @Override + public Content next() { + throw new NoSuchElementException("Cannot call next() on an empty iterator."); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Cannot remove from an empty iterator."); + } + }; + + private final Iterator iter; + private final boolean alltext; + + /** + * Create a Walker that preserves all content in its raw state. + * @param content the content to walk. + */ + public WalkerPRESERVE(final List content) { + super(); + if (content.isEmpty()) { + alltext = true; + iter = EMPTYIT; + } else { + iter = content.iterator(); + alltext = false; +// final int len = content.size(); +// boolean at = true; +// for (int i = 0 ; i < len && at ; i++) { +// switch (content.get(i).getCType()) { +// case Text: +// case CDATA: +// case EntityRef: +// break; +// default : +// at = false; +// break; +// } +// } +// alltext = at; + } + + } + + @Override + public boolean isAllText() { + return alltext; + } + + @Override + public boolean hasNext() { + return iter.hasNext(); + } + + @Override + public Content next() { + return iter.next(); + } + + @Override + public String text() { + return null; + } + + @Override + public boolean isCDATA() { + return false; + } + + @Override + public boolean isAllWhitespace() { + return alltext; + } + +} diff --git a/core/src/java/org/jdom/output/support/WalkerTRIM.java b/core/src/java/org/jdom/output/support/WalkerTRIM.java new file mode 100644 index 0000000..650f8ff --- /dev/null +++ b/core/src/java/org/jdom/output/support/WalkerTRIM.java @@ -0,0 +1,142 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import org.jdom.Content; +import org.jdom.Text; +import org.jdom.Verifier; + +/** + * This Walker implementation will produce trimmed text content. + * + * @author Rolf Lear + * + */ +public class WalkerTRIM extends AbstractFormattedWalker { + + /** + * Create the Trimmed walker instance. + * @param content The list of content to format + * @param fstack The current stack. + * @param escape Whether Text values should be escaped. + */ + public WalkerTRIM(final List content, + final FormatStack fstack, final boolean escape) { + super(content, fstack, escape); + } + + @Override + protected void analyzeMultiText(final MultiText mtext, + int offset, int len) { + + while (len > 0) { + final Content c = get(offset); + if (c instanceof Text) { + // either Text or CDATA + if (!Verifier.isAllXMLWhitespace(c.getValue())) { + break; + } + } else { + break; + } + offset++; + len--; + } + + while (len > 0) { + final Content c = get(offset + len - 1); + if (c instanceof Text) { + // either Text or CDATA + if (!Verifier.isAllXMLWhitespace(c.getValue())) { + break; + } + } else { + break; + } + len--; + } + + for (int i = 0; i < len; i++) { + Trim trim = Trim.NONE; + if (i + 1 == len) { + trim = Trim.RIGHT; + } + if (i == 0) { + trim = Trim.LEFT; + } + if (len == 1) { + trim = Trim.BOTH; + } + final Content c = get(offset + i); + switch (c.getCType()) { + case Text : + mtext.appendText(trim, c.getValue()); + break; + case CDATA : + mtext.appendCDATA(trim, c.getValue()); + break; + case EntityRef: + // treat like any other content. + // raw. + default: + mtext.appendRaw(c); + break; + } + } + + } +} diff --git a/core/src/java/org/jdom/output/support/WalkerTRIM_FULL_WHITE.java b/core/src/java/org/jdom/output/support/WalkerTRIM_FULL_WHITE.java new file mode 100644 index 0000000..2ec383e --- /dev/null +++ b/core/src/java/org/jdom/output/support/WalkerTRIM_FULL_WHITE.java @@ -0,0 +1,122 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.util.List; + +import org.jdom.Content; +import org.jdom.Text; +import org.jdom.Verifier; + +/** + * This Walker implementation will produce trimmed text content. + * + * @author Rolf Lear + * + */ +public class WalkerTRIM_FULL_WHITE extends AbstractFormattedWalker { + + /** + * Create the Trimmed walker instance. + * @param content The list of content to format + * @param fstack The current stack. + * @param escape Whether Text values should be escaped. + */ + public WalkerTRIM_FULL_WHITE(final List content, + final FormatStack fstack, final boolean escape) { + super(content, fstack, escape); + } + + @Override + protected void analyzeMultiText(final MultiText mtext, + final int offset, final int len) { + int ln = len; + while (--ln >= 0) { + final Content c = get(offset + ln); + if (c instanceof Text) { + // either Text or CDATA + if (!Verifier.isAllXMLWhitespace(c.getValue())) { + break; + } + } else { + break; + } + } + if (ln < 0) { + // all whitespace. + return; + } + + // some non-white, so return all, but merge the sequential Text items. + for (int i = 0; i < len; i++) { + final Content c = get(offset + i); + switch (c.getCType()) { + case Text : + mtext.appendText(Trim.NONE, c.getValue()); + break; + case CDATA : + mtext.appendCDATA(Trim.NONE, c.getValue()); + break; + case EntityRef: + // treat like any other content. + // raw. + default: + mtext.appendRaw(c); + break; + } + } + + } +} diff --git a/core/src/java/org/jdom/output/support/XMLOutputProcessor.java b/core/src/java/org/jdom/output/support/XMLOutputProcessor.java new file mode 100644 index 0000000..37c06cc --- /dev/null +++ b/core/src/java/org/jdom/output/support/XMLOutputProcessor.java @@ -0,0 +1,259 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.output.support; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; + +/** + * This interface provides a base support for the {@link XMLOutputter2}. + *

+ * People who want to create a custom XMLOutputProcessor for XMLOutputter are + * able to implement this interface with the following notes and restrictions: + *

    + *
  1. The XMLOutputter will call one, and only one of the process(Writer,Format,*) methods each + * time the XMLOutputter is requested to output some JDOM content. It is thus + * safe to assume that a process(Writer,Format,*) method can set up any + * infrastructure needed to process the content, and that the XMLOutputter will + * not re-call that method, or some other process(Writer,Format,*) method for the same output + * sequence. + *
  2. The process methods should be thread-safe and reentrant: The same + * process(Writer,Format,*) method may (will) be called concurrently from different threads. + *
+ *

+ * The {@link AbstractXMLOutputProcessor} class is a full implementation of this + * interface and is fully customisable. People who want a custom XMLOutputter + * are encouraged to extend the AbstractXMLOutputProcessor rather than do a full + * re-implementation of this interface. + * + * @see XMLOutputter2 + * @see AbstractXMLOutputProcessor + * @since JDOM2 + * @author Rolf Lear + */ +public interface XMLOutputProcessor { + + /** + * This will print the {@link Document} to the given Writer. + *

+ * Warning: using your own Writer may cause the outputter's preferred + * character encoding to be ignored. If you use encodings other than UTF-8, + * we recommend using the method that takes an OutputStream instead. + *

+ * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param doc + * Document to format. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, Document doc) throws IOException; + + /** + * Print out the {@link DocType}. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param doctype + * DocType to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, DocType doctype) throws IOException; + + /** + * Print out an {@link Element}, including its + * {@link Attribute}s, and all contained (child) elements, etc. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param element + * Element to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, Element element) throws IOException; + + /** + * This will handle printing out a list of nodes. This can be useful for + * printing the content of an element that contains HTML, like + * "<description>JDOM is <b>fun>!</description>". + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param list + * List of nodes. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input list is null or contains null members + * @throws ClassCastException + * if any of the list members are not {@link Content} + */ + public abstract void process(Writer out, Format format, List list) + throws IOException; + + /** + * Print out a {@link CDATA} node. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param cdata + * CDATA to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, CDATA cdata) throws IOException; + + /** + * Print out a {@link Text} node. Perfoms the necessary entity + * escaping and whitespace stripping. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param text + * Text to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, Text text) throws IOException; + + /** + * Print out a {@link Comment}. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param comment + * Comment to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, Comment comment) throws IOException; + + /** + * Print out a {@link ProcessingInstruction}. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param pi + * ProcessingInstruction to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, ProcessingInstruction pi) + throws IOException; + + /** + * Print out a {@link EntityRef}. + * + * @param out + * Writer to use. + * @param format + * Format instance specifying output style + * @param entity + * EntityRef to output. + * @throws IOException + * if there's any problem writing. + * @throws NullPointerException + * if the input content is null + */ + public abstract void process(Writer out, Format format, EntityRef entity) throws IOException; + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/output/support/package.html b/core/src/java/org/jdom/output/support/package.html new file mode 100644 index 0000000..bd60167 --- /dev/null +++ b/core/src/java/org/jdom/output/support/package.html @@ -0,0 +1,4 @@ + + Classes used to implement output functionality that are not part of the + actual Output API, but rather part of the implementation. + diff --git a/core/src/java/org/jdom/package.html b/core/src/java/org/jdom/package.html new file mode 100644 index 0000000..1b8fc91 --- /dev/null +++ b/core/src/java/org/jdom/package.html @@ -0,0 +1,67 @@ + + +Classes representing the components of an XML document. +

+In addition there are the Exceptions related to JDOM processing and some classes +useful for creating and accessing JDOM Content. +

+

Core JDOM classes

+All XML in JDOM is represented in the following classes: +
    +
  • Text - regular parsed XML character content (PCDATA). +
  • CDATA - unparsed XML text content (can contain < > and &). Note: in JDOM, CDATA class exends Text. +
  • Comment - XML Comments +
  • EntityRef - Entity References (e.g. &refeg; ) +
  • ProcessingInstruction - As the name suggests +
  • DocType - The relevant details of any DOCTYPE Declaration. +
  • Element - An XML element +
  • Document - A representation of a complete XML document +
+In addition to these 8 classes there are also the Attribute and Namespace +classes which are used to represent these respective XML structures in Element. +In the DOM model the 'Attr' (attribute) class is considered to be a DOM 'Node'. +In JDOM this is not the case - Attribute is not Content. +

+The XML Structure is embodied in the the concept of Parent JDOM classes and +regular JDOM Content. Parent is an interface, and Content is an abstract class. +The Document and Element classes are both Parent classes, Text, CDATA, Comment, +EntityRef, ProcessingInstruction, DocType and Element are all Content. Note that +Element is both Parent and Content. +

+To enforce XML well-formedness, Document is only allowed a restricted set of +child content: any number of ProcessingInstructions and Comments, one DocType, +and one Element (the 'root' element). Element is allowed any child content +except DocType. +

+The NamespaceAware interface identifies those JDOM constructs which are +sensitive to Namespaces, which is all 8 core types and also Attribute. In JDOM +NamespaceAware classes are able to identify and report the Namespace Context in +which they exist. +

+ +

JDOM helper classes

+

+The Verifier is a special class useful in ensuring well-formedness of documents. +It contains all the rules for ensuring the JDOM model always has well-formed +content. +

+JDOMConstants interface contains a number of constant values that JDOM users may +find useful when creating or manipulating JDOM structures. These are in +addition to (but some may duplicate) the constants found in the +javax.xml.XMLConstants class. +

+The JDOMFactory interface is primarily used when building JDOM documents from +some source (SAX, DOM, etc.) using one of the input Builders (SAXBuilder, +DOMBuilder, etc.). The default JDOMFactory is the DefaultJDOMFactory). +If you have custom JDOM classes or want special treatment for content as it is +being created you can supply you own JDOMFactory instance to the input Builder. +Typically you would extend the DefaultJDOMFactory for this purpose. +The DefaultJDOMFactory ensures all XML rules are followed correctly. The +UncheckedJDOMFactory may create JDOM content that does not follow XML +well-formedness rules. Use the UncheckedJDOMFactory in places where you are +certain the input is correct (perhaps the results of a document parsed by a +trusted third-party parser). The UncheckedJDOMParser is only marginally faster +than the DefaultJDOMParser. +

+ + diff --git a/core/src/java/org/jdom/transform/JDOMResult.java b/core/src/java/org/jdom/transform/JDOMResult.java new file mode 100644 index 0000000..2ce0184 --- /dev/null +++ b/core/src/java/org/jdom/transform/JDOMResult.java @@ -0,0 +1,692 @@ +/*-- + + Copyright (C) 2001-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.transform; + +import java.util.*; + +import javax.xml.transform.sax.*; + +import org.jdom.*; +import org.jdom.input.sax.SAXHandler; + +import org.xml.sax.*; +import org.xml.sax.ext.*; +import org.xml.sax.helpers.*; + +/** + * A holder for an XSL Transformation result, generally a list of nodes + * although it can be a JDOM Document also. As stated by the XSLT 1.0 + * specification, the result tree generated by an XSL transformation is not + * required to be a well-formed XML document. The result tree may have "any + * sequence of nodes as children that would be possible for an + * element node". + *

+ * The following example shows how to apply an XSL Transformation + * to a JDOM document and get the transformation result in the form + * of a list of JDOM nodes: + *


+ *   public static List transform(Document doc, String stylesheet)
+ *                                        throws JDOMException {
+ *     try {
+ *       Transformer transformer = TransformerFactory.newInstance()
+ *                             .newTransformer(new StreamSource(stylesheet));
+ *       JDOMSource in = new JDOMSource(doc);
+ *       JDOMResult out = new JDOMResult();
+ *       transformer.transform(in, out);
+ *       return out.getResult();
+ *     }
+ *     catch (TransformerException e) {
+ *       throw new JDOMException("XSLT Transformation failed", e);
+ *     }
+ *   }
+ * 
+ * + * @see org.jdom.transform.JDOMSource + * + * @author Laurent Bihanic + * @author Jason Hunter + */ +public class JDOMResult extends SAXResult { + + /** + * If {@link javax.xml.transform.TransformerFactory#getFeature} + * returns true when passed this value as an + * argument, the Transformer natively supports JDOM. + *

+ * Note: This implementation does not override + * the {@link SAXResult#FEATURE} value defined by its superclass + * to be considered as a SAXResult by Transformer implementations + * not natively supporting JDOM.

+ */ + public final static String JDOM_FEATURE = + JDOMConstants.JDOM2_FEATURE_JDOMRESULT; + + /** + * The result of a transformation, as set by Transformer + * implementations that natively support JDOM, as a JDOM document + * or a list of JDOM nodes. + */ + private List resultlist = null; + + private Document resultdoc = null; + + /** + * Whether the application queried the result (as a list or a + * document) since it was last set. + */ + private boolean queried = false; + + /** + * The custom JDOM factory to use when building the transformation + * result or null to use the default JDOM classes. + */ + private JDOMFactory factory = null; + + /** + * Public default constructor. + */ + public JDOMResult() { + // Allocate custom builder object... + DocumentBuilder builder = new DocumentBuilder(); + + // And use it as ContentHandler and LexicalHandler. + super.setHandler(builder); + super.setLexicalHandler(builder); + } + + /** + * Sets the object(s) produced as result of an XSL Transformation. + *

+ * Note: This method shall be used by the + * {@link javax.xml.transform.Transformer} implementations that + * natively support JDOM to directly set the transformation + * result rather than considering this object as a + * {@link SAXResult}. Applications should not use this + * method.

+ * + * @param result the result of a transformation as a + * {@link java.util.List list} of JDOM nodes + * (Elements, Texts, Comments, PIs...). + * + * @see #getResult + */ + public void setResult(List result) { + this.resultlist = result; + this.queried = false; + } + + /** + * Returns the result of an XSL Transformation as a list of JDOM + * nodes. + *

+ * If the result of the transformation is a JDOM document, + * this method converts it into a list of JDOM nodes; any + * subsequent call to {@link #getDocument} will return + * null.

+ * + * @return the transformation result as a (possibly empty) list of + * JDOM nodes (Elements, Texts, Comments, PIs...). + */ + public List getResult() { + List nodes = Collections.emptyList(); + + // Retrieve result from the document builder if not set. + this.retrieveResult(); + + if (resultlist != null) { + nodes = resultlist; + } + else { + if (resultdoc != null && queried == false) { + List content = resultdoc.getContent(); + nodes = new ArrayList(content.size()); + + while (content.size() != 0) + { + Content o = content.remove(0); + nodes.add(o); + } + resultlist = nodes; + resultdoc = null; + } + } + queried = true; + + return (nodes); + } + + /** + * Sets the document produced as result of an XSL Transformation. + *

+ * Note: This method shall be used by the + * {@link javax.xml.transform.Transformer} implementations that + * natively support JDOM to directly set the transformation + * result rather than considering this object as a + * {@link SAXResult}. Applications should not use this + * method.

+ * + * @param document the JDOM document result of a transformation. + * + * @see #setResult + * @see #getDocument + */ + public void setDocument(Document document) { + this.resultdoc = document; + this.resultlist = null; + this.queried = false; + } + + /** + * Returns the result of an XSL Transformation as a JDOM document. + *

+ * If the result of the transformation is a list of nodes, + * this method attempts to convert it into a JDOM document. If + * successful, any subsequent call to {@link #getResult} will + * return an empty list.

+ *

+ * Warning: The XSLT 1.0 specification states that + * the output of an XSL transformation is not a well-formed XML + * document but a list of nodes. Applications should thus use + * {@link #getResult} instead of this method or at least expect + * null documents to be returned. + * + * @return the transformation result as a JDOM document or + * null if the result of the transformation + * can not be converted into a well-formed document. + * + * @see #getResult + */ + public Document getDocument() { + Document doc = null; + + // Retrieve result from the document builder if not set. + this.retrieveResult(); + + if (resultdoc != null) { + doc = resultdoc; + } + else { + if (resultlist != null && (queried == false)) { + // Try to create a document from the result nodes + try { + JDOMFactory f = this.getFactory(); + if (f == null) { f = new DefaultJDOMFactory(); } + + doc = f.document(null); + doc.setContent(resultlist); + + resultdoc = doc; + resultlist = null; + } + catch (RuntimeException ex1) { + // Some of the result nodes are not valid children of a + // Document node. => return null. + return null; + } + } + } + queried = true; + + return (doc); + } + + /** + * Sets a custom JDOMFactory to use when building the + * transformation result. Use a custom factory to build the tree + * with your own subclasses of the JDOM classes. + * + * @param factory the custom JDOMFactory to use or + * null to use the default JDOM + * classes. + * + * @see #getFactory + */ + public void setFactory(JDOMFactory factory) { + this.factory = factory; + } + + /** + * Returns the custom JDOMFactory used to build the transformation + * result. + * + * @return the custom JDOMFactory used to build the + * transformation result or null if the + * default JDOM classes are being used. + * + * @see #setFactory + */ + public JDOMFactory getFactory() { + return this.factory; + } + + /** + * Checks whether a transformation result has been set and, if not, + * retrieves the result tree being built by the document builder. + */ + private void retrieveResult() { + if (resultlist == null && resultdoc == null) { + this.setResult(((DocumentBuilder)this.getHandler()).getResult()); + } + } + + //------------------------------------------------------------------------- + // SAXResult overwritten methods + //------------------------------------------------------------------------- + + /** + * Sets the target to be a SAX2 ContentHandler. + * + * @param handler Must be a non-null ContentHandler reference. + */ + @Override + public void setHandler(ContentHandler handler) { + // Do Nothing + } + + /** + * Sets the SAX2 LexicalHandler for the output. + *

+ * This is needed to handle XML comments and the like. If the + * lexical handler is not set, an attempt should be made by the + * transformer to cast the ContentHandler to a LexicalHandler.

+ * + * @param handler A non-null LexicalHandler for + * handling lexical parse events. + */ + @Override + public void setLexicalHandler(LexicalHandler handler) { + // Ignore. + } + + + //========================================================================= + // FragmentHandler nested class + //========================================================================= + + private static class FragmentHandler extends SAXHandler { + /** + * A dummy root element required by SAXHandler that can only + * cope with well-formed documents. + */ + private Element dummyRoot = new Element("root", null, null); + + /** + * Public constructor. + * @param factory The Factory to use to create content instances + */ + public FragmentHandler(JDOMFactory factory) { + super(factory); + + // Add a dummy root element to the being-built document as XSL + // transformation can output node lists instead of well-formed + // documents. + this.pushElement(dummyRoot); + } + + /** + * Returns the result of an XSL Transformation. + * + * @return the transformation result as a (possibly empty) list of + * JDOM nodes (Elements, Texts, Comments, PIs...). + */ + public List getResult() { + // Flush remaining text content in case the last text segment is + // outside an element. + try { + this.flushCharacters(); + } + catch (SAXException e) { /* Ignore... */ } + return this.getDetachedContent(dummyRoot); + } + + /** + * Returns the content of a JDOM Element detached from it. + * + * @param elt the element to get the content from. + * + * @return a (possibly empty) list of JDOM nodes, detached from + * their parent. + */ + private List getDetachedContent(Element elt) { + List content = elt.getContent(); + List nodes = new ArrayList(content.size()); + + while (content.size() != 0) + { + Content o = content.remove(0); + nodes.add(o); + } + return (nodes); + } + } + + //========================================================================= + // DocumentBuilder inner class + //========================================================================= + + private class DocumentBuilder extends XMLFilterImpl + implements LexicalHandler { + /** + * The actual JDOM document builder. + */ + private FragmentHandler saxHandler = null; + + /** + * Whether the startDocument event was received. Some XSLT + * processors such as Oracle's do not fire this event. + */ + private boolean startDocumentReceived = false; + + /** + * Public default constructor. + */ + public DocumentBuilder() { } + + /** + * Returns the result of an XSL Transformation. + * + * @return the transformation result as a (possibly empty) list of + * JDOM nodes (Elements, Texts, Comments, PIs...) or + * null if no new transformation occurred + * since the result of the previous one was returned. + */ + public List getResult() { + List mresult = null; + + if (this.saxHandler != null) { + // Retrieve result from SAX content handler. + mresult = this.saxHandler.getResult(); + + // Detach the (non-reusable) SAXHandler instance. + this.saxHandler = null; + + // And get ready for the next transformation. + this.startDocumentReceived = false; + } + return mresult; + } + + private void ensureInitialization() throws SAXException { + // Trigger document initialization if XSLT processor failed to + // fire the startDocument event. + if (this.startDocumentReceived == false) { + this.startDocument(); + } + } + + //----------------------------------------------------------------------- + // XMLFilterImpl overwritten methods + //----------------------------------------------------------------------- + + /** + * [SAX ContentHandler interface support] Processes a + * start of document event. + *

+ * This implementation creates a new JDOM document builder and + * marks the current result as "under construction".

+ * + * @throws SAXException if any error occurred while creating + * the document builder. + */ + @Override + public void startDocument() throws SAXException { + this.startDocumentReceived = true; + + // Reset any previously set result. + setResult(null); + + // Create the actual JDOM document builder and register it as + // ContentHandler on the superclass (XMLFilterImpl): this + // implementation will take care of propagating the LexicalHandler + // events. + this.saxHandler = new FragmentHandler(getFactory()); + super.setContentHandler(this.saxHandler); + + // And propagate event. + super.startDocument(); + } + + /** + * [SAX ContentHandler interface support] Receives + * notification of the beginning of an element. + *

+ * This implementation ensures that startDocument() has been + * called prior processing an element. + * + * @param nsURI the Namespace URI, or the empty string if + * the element has no Namespace URI or if + * Namespace processing is not being performed. + * @param localName the local name (without prefix), or the + * empty string if Namespace processing is + * not being performed. + * @param qName the qualified name (with prefix), or the + * empty string if qualified names are not + * available. + * @param atts The attributes attached to the element. If + * there are no attributes, it shall be an + * empty Attributes object. + * + * @throws SAXException if any error occurred while creating + * the document builder. + */ + @Override + public void startElement(String nsURI, String localName, String qName, + Attributes atts) throws SAXException + { + this.ensureInitialization(); + super.startElement(nsURI, localName, qName, atts); + } + + /** + * [SAX ContentHandler interface support] Begins the + * scope of a prefix-URI Namespace mapping. + */ + @Override + public void startPrefixMapping(String prefix, String uri) + throws SAXException { + this.ensureInitialization(); + super.startPrefixMapping(prefix, uri); + } + + /** + * [SAX ContentHandler interface support] Receives + * notification of character data. + */ + @Override + public void characters(char ch[], int start, int length) + throws SAXException { + this.ensureInitialization(); + super.characters(ch, start, length); + } + + /** + * [SAX ContentHandler interface support] Receives + * notification of ignorable whitespace in element content. + */ + @Override + public void ignorableWhitespace(char ch[], int start, int length) + throws SAXException { + this.ensureInitialization(); + super.ignorableWhitespace(ch, start, length); + } + + /** + * [SAX ContentHandler interface support] Receives + * notification of a processing instruction. + */ + @Override + public void processingInstruction(String target, String data) + throws SAXException { + this.ensureInitialization(); + super.processingInstruction(target, data); + } + + /** + * [SAX ContentHandler interface support] Receives + * notification of a skipped entity. + */ + @Override + public void skippedEntity(String name) throws SAXException { + this.ensureInitialization(); + super.skippedEntity(name); + } + + //----------------------------------------------------------------------- + // LexicalHandler interface support + //----------------------------------------------------------------------- + + /** + * [SAX LexicalHandler interface support] Reports the + * start of DTD declarations, if any. + * + * @param name the document type name. + * @param publicId the declared public identifier for the + * external DTD subset, or null + * if none was declared. + * @param systemId the declared system identifier for the + * external DTD subset, or null + * if none was declared. + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void startDTD(String name, String publicId, String systemId) + throws SAXException { + this.ensureInitialization(); + this.saxHandler.startDTD(name, publicId, systemId); + } + + /** + * [SAX LexicalHandler interface support] Reports the end + * of DTD declarations. + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void endDTD() throws SAXException { + this.saxHandler.endDTD(); + } + + /** + * [SAX LexicalHandler interface support] Reports the + * beginning of some internal and external XML entities. + * + * @param name the name of the entity. If it is a parameter + * entity, the name will begin with '%', and if it + * is the external DTD subset, it will be "[dtd]". + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void startEntity(String name) throws SAXException { + this.ensureInitialization(); + this.saxHandler.startEntity(name); + } + + /** + * [SAX LexicalHandler interface support] Reports the end + * of an entity. + * + * @param name the name of the entity that is ending. + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void endEntity(String name) throws SAXException { + this.saxHandler.endEntity(name); + } + + /** + * [SAX LexicalHandler interface support] Reports the + * start of a CDATA section. + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void startCDATA() throws SAXException { + this.ensureInitialization(); + this.saxHandler.startCDATA(); + } + + /** + * [SAX LexicalHandler interface support] Reports the end + * of a CDATA section. + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void endCDATA() throws SAXException { + this.saxHandler.endCDATA(); + } + + /** + * [SAX LexicalHandler interface support] Reports an XML + * comment anywhere in the document. + * + * @param ch an array holding the characters in the comment. + * @param start the starting position in the array. + * @param length the number of characters to use from the array. + * + * @throws SAXException The application may raise an exception. + */ + @Override + public void comment(char ch[], int start, int length) + throws SAXException { + this.ensureInitialization(); + this.saxHandler.comment(ch, start, length); + } + } +} + diff --git a/core/src/java/org/jdom/transform/JDOMSource.java b/core/src/java/org/jdom/transform/JDOMSource.java new file mode 100644 index 0000000..d16fa89 --- /dev/null +++ b/core/src/java/org/jdom/transform/JDOMSource.java @@ -0,0 +1,575 @@ +/*-- + + Copyright (C) 2001-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.transform; + +import java.io.InputStream; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.transform.sax.SAXSource; + +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMConstants; +import org.jdom.JDOMException; +import org.jdom.output.SAXOutputter; +import org.jdom.output.XMLOutputter2; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLFilter; +import org.xml.sax.XMLReader; + +/** + * A holder for an XML Transformation source: a Document, Element, or list of + * nodes. + *

+ * The is provides input to a + * {@link javax.xml.transform.Transformer JAXP TrAX Transformer}. + *

+ * The following example shows how to apply an XSL Transformation + * to a JDOM document and get the transformation result in the form + * of a list of JDOM nodes: + *


+ *   public static List transform(Document doc, String stylesheet)
+ *                                        throws JDOMException {
+ *     try {
+ *       Transformer transformer = TransformerFactory.newInstance()
+ *                             .newTransformer(new StreamSource(stylesheet));
+ *       JDOMSource in = new JDOMSource(doc);
+ *       JDOMResult out = new JDOMResult();
+ *       transformer.transform(in, out);
+ *       return out.getResult();
+ *     }
+ *     catch (TransformerException e) {
+ *       throw new JDOMException("XSLT Transformation failed", e);
+ *     }
+ *   }
+ * 
+ * + * @see org.jdom.transform.JDOMResult + * + * @author Laurent Bihanic + * @author Jason Hunter + */ +public class JDOMSource extends SAXSource { + + /** + * If {@link javax.xml.transform.TransformerFactory#getFeature} + * returns true when passed this value as an + * argument, the Transformer natively supports JDOM. + *

+ * Note: This implementation does not override + * the {@link SAXSource#FEATURE} value defined by its superclass + * to be considered as a SAXSource by Transformer implementations + * not natively supporting JDOM. + *

+ */ + public final static String JDOM_FEATURE = JDOMConstants.JDOM2_FEATURE_JDOMSOURCE; + + + /** + * The XMLReader object associated to this source or + * null if no XMLReader has yet been requested. + * + * @see #getXMLReader + */ + private XMLReader xmlReader = null; + + /** + * Optional entity resolver associated to the source of + * this document or null if no EntityResolver + * was supplied with this JDOMSource. + * + * @see #buildDocumentReader() + */ + private EntityResolver resolver = null; + + /** + * Creates a JDOM TrAX source wrapping a JDOM document. + * + * @param source the JDOM document to use as source for the + * transformations + * + * @throws IllegalArgumentException if source is + * null. + */ + public JDOMSource(Document source) { + this(source, null); + } + + /** + * Creates a JDOM TrAX source wrapping a list of JDOM nodes. + * + * @param source the JDOM nodes to use as source for the + * transformations + * + * @throws IllegalArgumentException if source is + * null. + */ + public JDOMSource(List source) { + setNodes(source); + } + + /** + * Creates a JDOM TrAX source wrapping a JDOM element. + * + * @param source the JDOM element to use as source for the + * transformations + * + * @throws IllegalArgumentException if source is + * null. + */ + public JDOMSource(Element source) { + List nodes = new ArrayList(); + nodes.add(source); + + setNodes(nodes); + } + + /** + * Creates a JDOM TrAX source wrapping a JDOM element with an + * associated EntityResolver to resolve external entities. + * + * @param source The JDOM Element to use as source for the + * transformations + * + * @param resolver Entity resolver to use for the source + * transformation + * + * @throws IllegalArgumentException ifsource is + * null + */ + public JDOMSource(Document source, EntityResolver resolver) { + setDocument(source); + this.resolver = resolver; + if (source != null && source.getBaseURI() != null) { + super.setSystemId(source.getBaseURI()); + } + } + + /** + * Sets the source document used by this TrAX source. + * + * @param source the JDOM document to use as source for the + * transformations + * + * @throws IllegalArgumentException if source is + * null. + * + * @see #getDocument + */ + public void setDocument(Document source) { + super.setInputSource(new JDOMInputSource(source)); + } + + /** + * Returns the source document used by this TrAX source. + * + * @return the source document used by this TrAX source or + * null if the source is a node list. + * + * @see #setDocument + */ + public Document getDocument() { + Object src = ((JDOMInputSource)getInputSource()).getSource(); + Document doc = null; + + if (src instanceof Document) { + doc = (Document)src; + } + return doc; + } + + /** + * Sets the source node list used by this TrAX source. + * + * @param source the JDOM nodes to use as source for the + * transformations + * + * @throws IllegalArgumentException if source is + * null. + * + * @see #getNodes + */ + public void setNodes(List source) { + super.setInputSource(new JDOMInputSource(source)); + } + + /** + * Returns the source node list used by this TrAX source. + * + * @return the source node list used by this TrAX source or + * null if the source is a JDOM document. + * + * @see #setDocument + */ + public List getNodes() { + return ((JDOMInputSource)getInputSource()).getListSource(); + } + + + //------------------------------------------------------------------------- + // SAXSource overwritten methods + //------------------------------------------------------------------------- + + /** + * Sets the SAX InputSource to be used for the Source. + *

+ * As this implementation only supports JDOM document as data + * source, this method always throws an + * {@link UnsupportedOperationException}. + *

+ * + * @param inputSource a valid InputSource reference. + * + * @throws UnsupportedOperationException always! + */ + @Override + public void setInputSource(InputSource inputSource) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + /** + * Set the XMLReader to be used for the Source. + *

+ * As this implementation only supports JDOM document as data + * source, this method throws an + * {@link UnsupportedOperationException} if the provided reader + * object does not implement the SAX {@link XMLFilter} + * interface. Otherwise, the JDOM document reader will be + * attached as parent of the filter chain.

+ * + * @param reader a valid XMLReader or XMLFilter reference. + * + * @throws UnsupportedOperationException if reader + * is not a SAX + * {@link XMLFilter}. + * @see #getXMLReader + */ + @Override + public void setXMLReader(XMLReader reader) + throws UnsupportedOperationException { + if (reader instanceof XMLFilter) { + // Connect the filter chain to a document reader. + XMLFilter filter = (XMLFilter)reader; + while (filter.getParent() instanceof XMLFilter) { + filter = (XMLFilter)(filter.getParent()); + } + filter.setParent(buildDocumentReader()); + + // Read XML data from filter chain. + this.xmlReader = reader; + } + else { + throw new UnsupportedOperationException(); + } + } + + /** + * Returns the XMLReader to be used for the Source. + *

+ * This implementation returns a specific XMLReader reading + * the XML data from the source JDOM document. + *

+ * + * @return an XMLReader reading the XML data from the source + * JDOM document. + */ + @Override + public XMLReader getXMLReader() { + if (this.xmlReader == null) { + this.xmlReader = buildDocumentReader(); + } + return this.xmlReader; + } + + /** + * Build an XMLReader to be used for the source. This will + * create a new instance of DocumentReader with an + * EntityResolver instance if available. + * + * @return XMLReader reading the XML data from the source + * JDOM document with an optional EntityResolver + */ + private XMLReader buildDocumentReader() { + DocumentReader reader = new DocumentReader(); + if (resolver != null) + reader.setEntityResolver(resolver); + return reader; + } + + //========================================================================= + // JDOMInputSource nested class + //========================================================================= + + /** + * A subclass of the SAX InputSource interface that wraps a JDOM + * Document. + *

+ * This class is nested in JDOMSource as it is not intented to + * be used independently of its friend: DocumentReader. + *

+ * + * @see org.jdom.Document + */ + private static class JDOMInputSource extends InputSource { + /** + * The source as a JDOM document or a list of JDOM nodes. + */ + private Document docsource = null; + + /** + * The source as a JDOM document or a list of JDOM nodes. + */ + private List listsource = null; + /** + * Builds a InputSource wrapping the specified JDOM Document. + * + * @param document the source document. + */ + public JDOMInputSource(Document document) { + this.docsource = document; + } + + /** + * Builds a InputSource wrapping a list of JDOM nodes. + * + * @param nodes the source JDOM nodes. + */ + public JDOMInputSource(List nodes) { + this.listsource = nodes; + } + + /** + * Returns the source. + * + * @return the source as a JDOM document or a list of JDOM nodes. + */ + public Object getSource() { + return docsource == null ? listsource : docsource; + } + + //------------------------------------------------------------------------- + // InputSource overwritten methods + //------------------------------------------------------------------------- + + /** + * Sets the character stream for this input source. + *

+ * This implementation always throws an + * {@link UnsupportedOperationException} as the only source + * stream supported is the source JDOM document. + *

+ * + * @param characterStream a character stream containing + * an XML document. + * + * @throws UnsupportedOperationException always! + */ + @Override + public void setCharacterStream(Reader characterStream) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + /** + * Gets the character stream for this input source. + *

+ * Note that this method is only provided to make this + * InputSource implementation acceptable by any XML + * parser. As it generates an in-memory string representation + * of the JDOM document, it is quite inefficient from both + * speed and memory consumption points of view. + *

+ * + * @return a Reader to a string representation of the + * source JDOM document. + */ + @Override + public Reader getCharacterStream() { + Reader reader = null; + + if (docsource != null) { + // Get an in-memory string representation of the document + // and return a reader on it. + reader = new StringReader( + new XMLOutputter2().outputString(docsource)); + } + else if (listsource != null) { + reader = new StringReader( + new XMLOutputter2().outputString(listsource)); + } + // Else: No source, no reader! + return reader; + } + /** + * Sets the byte stream for this input source. + *

+ * This implementation always throws an + * {@link UnsupportedOperationException} as the only source + * stream supported is the source JDOM document. + *

+ * + * @param byteStream a byte stream containing + * an XML document. + * + * @throws UnsupportedOperationException always! + */ + @Override + public void setByteStream(InputStream byteStream) + throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + public Document getDocumentSource() { + return docsource; + } + + public List getListSource() { + // TODO Auto-generated method stub + return listsource; + } + + } + + //========================================================================= + // DocumentReader nested class + //========================================================================= + + /** + * An implementation of the SAX2 XMLReader interface that presents + * a SAX view of a JDOM Document. The actual generation of the + * SAX events is delegated to JDOM's SAXOutputter. + * + * @see org.jdom.Document + * @see org.jdom.output.SAXOutputter + */ + private static class DocumentReader extends SAXOutputter + implements XMLReader { + /** + * Public default constructor. + */ + public DocumentReader() { + super(); + } + + //---------------------------------------------------------------------- + // SAX XMLReader interface support + //---------------------------------------------------------------------- + + /** + * Parses an XML document from a system identifier (URI). + *

+ * This implementation does not support reading XML data from + * system identifiers, only from JDOM documents. Hence, + * this method always throws a {@link SAXNotSupportedException}. + *

+ * + * @param systemId the system identifier (URI). + * + * @throws SAXNotSupportedException always! + */ + @Override + public void parse(String systemId) throws SAXNotSupportedException { + throw new SAXNotSupportedException( + "Only JDOM Documents are supported as input"); + } + + /** + * Parses an XML document. + *

+ * The methods accepts only JDOMInputSources + * instances as input sources. + *

+ * + * @param input the input source for the top-level of the + * XML document. + * + * @throws SAXException any SAX exception, + * possibly wrapping + * another exception. + * @throws SAXNotSupportedException if the input source does + * not wrap a JDOM document. + */ + @Override + public void parse(InputSource input) throws SAXException { + if (input instanceof JDOMInputSource) { + try { + Document docsource = ((JDOMInputSource)input).getDocumentSource(); + if (docsource != null) { + this.output(docsource); + } + else { + this.output(((JDOMInputSource)input).getListSource()); + } + } + catch (JDOMException e) { + throw new SAXException(e.getMessage(), e); + } + } + else { + throw new SAXNotSupportedException( + "Only JDOM Documents are supported as input"); + } + } + } +} + diff --git a/core/src/java/org/jdom/transform/XSLTransformException.java b/core/src/java/org/jdom/transform/XSLTransformException.java new file mode 100644 index 0000000..e8e1243 --- /dev/null +++ b/core/src/java/org/jdom/transform/XSLTransformException.java @@ -0,0 +1,93 @@ +/*-- + + Copyright (C) 2003-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.transform; + +import org.jdom.JDOMException; + +/** + * Thrown when an XSL stylesheet fails to compile or an XSL transform fails + * + * @author Jason Hunter + */ +public class XSLTransformException extends JDOMException { + + /** + * Standard JDOM2 Exception Serialization. Default. + */ + private static final long serialVersionUID = 200L; + + /** + * A new and default XSLTransformException + */ + public XSLTransformException() { + } + + /** + * A new XSLTransformException with the specified message + * @param message The message for the exception + */ + public XSLTransformException(String message) { + super(message); + } + + /** + * A new XSLTransformException with the specified message and cause + * @param message The message for the exception + * @param cause This exception's cause. + */ + public XSLTransformException(String message, Exception cause) { + super(message, cause); + } +} diff --git a/core/src/java/org/jdom/transform/XSLTransformer.java b/core/src/java/org/jdom/transform/XSLTransformer.java new file mode 100644 index 0000000..0660f32 --- /dev/null +++ b/core/src/java/org/jdom/transform/XSLTransformer.java @@ -0,0 +1,285 @@ +/*-- + + Copyright (C) 2001-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.transform; + +import java.util.*; +import java.io.*; +import javax.xml.transform.*; +import javax.xml.transform.stream.StreamSource; +import org.jdom.*; +import org.xml.sax.EntityResolver; + +/** + * A convenience class to handle simple transformations. The JAXP TrAX classes + * have more bells and whistles and can be used with {@link JDOMSource} and + * {@link JDOMResult} for advanced uses. This class handles the common case and + * presents a simple interface. XSLTransformer is thread safe and may be + * used from multiple threads. + * + *

+ * XSLTransformer transformer = new XSLTransformer("file.xsl");
+ *
+ * Document x2 = transformer.transform(x);  // x is a Document
+ * Document y2 = transformer.transform(y);  // y is a Document
+ * 
+ * + * JDOM relies on TrAX to perform the transformation. + * The javax.xml.transform.TransformerFactory Java system property + * determines which XSLT engine TrAX uses. Its value should be + * the fully qualified name of the implementation of the abstract + * javax.xml.transform.TransformerFactory class. + * Values of this property for popular XSLT processors include: + *

+ *
  • Saxon 6.x: com.icl.saxon.TransformerFactoryImpl
  • + *
  • Saxon 7.x: net.sf.saxon.TransformerFactoryImpl
  • + *
  • Xalan: org.apache.xalan.processor.TransformerFactoryImpl
  • + *
  • jd.xslt: jd.xml.xslt.trax.TransformerFactoryImpl
  • + *
  • Oracle: oracle.xml.jaxp.JXSAXTransformerFactory
  • + *
+ *

+ * This property can be set in all the usual ways a Java system property + * can be set. TrAX picks from them in this order:

+ *
    + *
  1. Invoking System.setProperty( "javax.xml.transform.TransformerFactory", + * "classname")
  2. + *
  3. The value specified at the command line using the + * -Djavax.xml.transform.TransformerFactory=classname + * option to the java interpreter
  4. + *
  5. The class named in the lib/jaxp.properties properties file + * in the JRE directory, in a line like this one: + *
    javax.xml.parsers.DocumentBuilderFactory=classname
  6. + *
  7. The class named in the + * META-INF/services/javax.xml.transform.TransformerFactory file + * in the JAR archives available to the runtime
  8. + *
  9. Finally, if all of the above options fail, + * a default implementation is chosen. In Sun's JDK 1.4, this is + * Xalan 2.2d10.
  10. + *
+ + * @author Jason Hunter + * @author Elliotte Rusty Harold + */ +public class XSLTransformer { + + private Templates templates; + + /** + * The custom JDOM factory to use when building the transformation + * result or null to use the default JDOM classes. + */ + private JDOMFactory factory = null; + + // Internal constructor to support the other constructors + private XSLTransformer(Source stylesheet) throws XSLTransformException { + try { + templates = TransformerFactory.newInstance() + .newTemplates(stylesheet); + } + catch (TransformerException e) { + throw new XSLTransformException("Could not construct XSLTransformer", e); + } + } + + /** + * Creates a transformer for a given stylesheet system id. + * + * @param stylesheetSystemId source stylesheet as a Source object + * @throws XSLTransformException if there's a problem in the TrAX back-end + */ + public XSLTransformer(String stylesheetSystemId) throws XSLTransformException { + this(new StreamSource(stylesheetSystemId)); + } + + /** + *

+ * This will create a new XSLTransformer by + * reading the stylesheet from the specified + * InputStream. + *

+ * + * @param stylesheet InputStream from which the stylesheet is read. + * @throws XSLTransformException when an IOException, format error, or + * something else prevents the stylesheet from being compiled + */ + public XSLTransformer(InputStream stylesheet) throws XSLTransformException { + this(new StreamSource(stylesheet)); + } + + /** + *

+ * This will create a new XSLTransformer by + * reading the stylesheet from the specified + * Reader. + *

+ * + * @param stylesheet Reader from which the stylesheet is read. + * @throws XSLTransformException when an IOException, format error, or + * something else prevents the stylesheet from being compiled + */ + public XSLTransformer(Reader stylesheet) throws XSLTransformException { + this(new StreamSource(stylesheet)); + } + + /** + *

+ * This will create a new XSLTransformer by + * reading the stylesheet from the specified + * File. + *

+ * + * @param stylesheet File from which the stylesheet is read. + * @throws XSLTransformException when an IOException, format error, or + * something else prevents the stylesheet from being compiled + */ + public XSLTransformer(File stylesheet) throws XSLTransformException { + this(new StreamSource(stylesheet)); + } + + /** + *

+ * This will create a new XSLTransformer by + * reading the stylesheet from the specified + * Document. + *

+ * + * @param stylesheet Document containing the stylesheet. + * @throws XSLTransformException when the supplied Document + * is not syntactically correct XSLT + */ + public XSLTransformer(Document stylesheet) throws XSLTransformException { + this(new JDOMSource(stylesheet)); + } + + /** + * Transforms the given input nodes to a list of output nodes. + * + * @param inputNodes input nodes + * @return transformed output nodes + * @throws XSLTransformException if there's a problem in the transformation + */ + public List transform(List inputNodes) throws XSLTransformException { + JDOMSource source = new JDOMSource(inputNodes); + JDOMResult result = new JDOMResult(); + result.setFactory(factory); // null ok + try { + templates.newTransformer().transform(source, result); + return result.getResult(); + } + catch (TransformerException e) { + throw new XSLTransformException("Could not perform transformation", e); + } + } + + /** + * Transforms the given document to an output document. + * + * @param inputDoc input document + * @return transformed output document + * @throws XSLTransformException if there's a problem in the transformation + */ + public Document transform(Document inputDoc) throws XSLTransformException { + return transform(inputDoc, null); + } + + /** + * Transforms the given document to an output document. + * + * @param inputDoc input document + * @param resolver entity resolver for the input document + * @return transformed output document + * @throws XSLTransformException if there's a problem in the transformation + */ + public Document transform(Document inputDoc, EntityResolver resolver) throws XSLTransformException { + JDOMSource source = new JDOMSource(inputDoc, resolver); + JDOMResult result = new JDOMResult(); + result.setFactory(factory); // null ok + try { + templates.newTransformer().transform(source, result); + return result.getDocument(); + } + catch (TransformerException e) { + throw new XSLTransformException("Could not perform transformation", e); + } + } + + /** + * Sets a custom JDOMFactory to use when building the + * transformation result. Use a custom factory to build the tree + * with your own subclasses of the JDOM classes. + * + * @param factory the custom JDOMFactory to use or + * null to use the default JDOM + * classes. + * + * @see #getFactory + */ + public void setFactory(JDOMFactory factory) { + this.factory = factory; + } + + /** + * Returns the custom JDOMFactory used to build the transformation + * result. + * + * @return the custom JDOMFactory used to build the + * transformation result or null if the + * default JDOM classes are being used. + * + * @see #setFactory + */ + public JDOMFactory getFactory() { + return this.factory; + } +} diff --git a/core/src/java/org/jdom/transform/package.html b/core/src/java/org/jdom/transform/package.html new file mode 100644 index 0000000..c20b50e --- /dev/null +++ b/core/src/java/org/jdom/transform/package.html @@ -0,0 +1,8 @@ + + +Classes to help with transformations, based on the JAXP TrAX classes. +JDOMTransformer supports simple transformations with one line of code. +Advanced features are available with the JDOMSource and JDOMResult classes +that interface with TrAX. + + diff --git a/core/src/java/org/jdom/util/IteratorIterable.java b/core/src/java/org/jdom/util/IteratorIterable.java new file mode 100644 index 0000000..285c735 --- /dev/null +++ b/core/src/java/org/jdom/util/IteratorIterable.java @@ -0,0 +1,75 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.util; + +import java.util.Iterator; + +/** + * An interface that represents both a java.util.Iterator + * and a java.lang.Iterable. + *

+ * JDOM 1.x has a number of methods that return an Iterator. These methods + * would (in some conditions) be better represented as an Iterable. To maintain + * compatibility, and to extend the functionality of these methods in JDOM2, + * they have been altered to return an instance of this interface. + * + * @author Rolf Lear + * + * @param The generic type of the values returned by this interface + */ +public interface IteratorIterable extends Iterable, Iterator { + // There is no functionality added by this interface other than + // to combine the Iterator and Iterable interfaces. +} diff --git a/core/src/java/org/jdom/util/NamespaceStack.java b/core/src/java/org/jdom/util/NamespaceStack.java new file mode 100644 index 0000000..df7715e --- /dev/null +++ b/core/src/java/org/jdom/util/NamespaceStack.java @@ -0,0 +1,602 @@ +/*-- + + Copyright (C) 2011 - 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.jdom.Attribute; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.internal.ArrayCopy; + + +/** + * A high-performance stack for processing those Namespaces that are introduced + * or are in-scope at a point in an Element hierarchy. + *

+ * This stack implements the 'Namespace Rules' which XML uses, where a Namespace + * 'redefines' an existing Namespace if they share the same prefix. This class + * is intended to provide a high-performance mechanism for calculating the + * Namespace scope for an Element, and identifying what Namespaces an Element + * introduces in to the scope. This is not a validation tool. + *

+ * This class implements Iterable which means it can be used in the context + * of a for-each type loop: + *
+ *

+ *   NamespaceStack namespacestack = new NamespaceStack();
+ *   for (Namespace ns : namespacestack) {
+ *      ...
+ *   }
+ * 
+ * The Iteration in the above example will return those Namespaces which are + * in-scope for the current level of the stack. The Namespace order will follow + * the JDOM 'standard'. The first namespace will be the Element's Namespace. The + * subsequent Namespaces will be the other in-scope namespaces in alphabetical + * order by the Namespace prefix. + *

+ * NamespaceStack does not validate the push()/pop() cycles. It does not ensure + * that the pop() is for the same element that was previously pushed. Further, + * it does not check to make sure that the pushed() Element is the natural child + * of the previously pushed() Element. + * + * @author Rolf Lear + * + */ +public final class NamespaceStack implements Iterable { + + /** + * Simple read-only iterator that walks an array of Namespace. + * + * @author rolf + * + */ + private static final class ForwardWalker implements Iterator { + private final Namespace[] namespaces; + int cursor = 0; + + public ForwardWalker(Namespace[] namespaces) { + this.namespaces = namespaces; + } + + @Override + public boolean hasNext() { + return cursor < namespaces.length; + } + + @Override + public Namespace next() { + if (cursor >= namespaces.length) { + throw new NoSuchElementException("Cannot over-iterate..."); + } + return namespaces[cursor++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove Namespaces from iterator"); + + } + + } + + /** + * Simple read-only iterator that walks an array of Namespace in reverse. + * + * @author rolf + * + */ + private static final class BackwardWalker implements Iterator { + private final Namespace[] namespaces; + int cursor = -1; + + public BackwardWalker(Namespace[] namespaces) { + this.namespaces = namespaces; + cursor = namespaces.length - 1; + } + + @Override + public boolean hasNext() { + return cursor >= 0; + } + + @Override + public Namespace next() { + if (cursor < 0) { + throw new NoSuchElementException("Cannot over-iterate..."); + } + return namespaces[cursor--]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove Namespaces from iterator"); + } + + } + + /** + * Simple Iterable instance that produces either Forward or Backward + * read-only iterators of the Namespaces + * + * @author rolf + * + */ + private static final class NamespaceIterable implements Iterable { + private final boolean forward; + private final Namespace[] namespaces; + public NamespaceIterable(Namespace[] data, boolean forward) { + this.forward = forward; + this.namespaces = data; + } + @Override + public Iterator iterator() { + return forward ? new ForwardWalker(namespaces) + : new BackwardWalker(namespaces); + } + } + + /** + * Convenience class that makes very fast work for an empty Namespace array. + * It doubles up as both Iterator and Interable. + * @author rolf + */ + private static final class EmptyIterable + implements Iterable, Iterator { + @Override + public Iterator iterator() { + return this; + } + + @Override + public boolean hasNext() { + return false; + } + + @Override + public Namespace next() { + throw new NoSuchElementException( + "Can not call next() on an empty Iterator."); + } + + @Override + public void remove() { + throw new UnsupportedOperationException( + "Cannot remove Namespaces from iterator"); + } + } + + /** A simple empty Namespace Array to avoid redundant empty instances */ + private static final Namespace[] EMPTY = new Namespace[0]; + /** A simple Iterable instance that is always empty. Saves some memory */ + private static final Iterable EMPTYITER = new EmptyIterable(); + + /** A comparator that sorts Namespaces by their prefix. */ + private static final Comparator NSCOMP = new Comparator() { + @Override + public int compare(Namespace ns1, Namespace ns2) { + return ns1.getPrefix().compareTo(ns2.getPrefix()); + } + }; + private static final Namespace[] DEFAULTSEED = new Namespace[] { + Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE}; + + /** + * Lots of reasons for having our own binarySearch. + *

    + *
  • We can make it specific for Namespaces (using == search). + *
  • There is a bug in IBM's AIX JVM in all Java's prior to (including): + * IBM J9 VM (build 2.4, J2RE 1.6.0 IBM J9 2.4 AIX ppc-32 + * jvmap3260-20081105_25433 (JIT enabled, AOT enabled)) + * where it returns '-1' for all instances where 'from == to' instead + * of returning '-from -1'. See + * + * this description for how it is broken, and pre-checking to make + * sure that left < right for each test is a pain. + *
  • Ahh, actually, we will never encounter the bug, because we always + * have a larger-than-1 scope array.... see comment inside code... + *
  • It's not that complicated, really. + *
+ * @param data The Namespaces to search. + * @param left The left side of the range to search INCLUSIVE + * @param right The right side of the range to search EXCLUSIVE + * @param key The Namespace to search for. + * @return the 'insertion point' - This return value follows the same convention + * as the standard Java BinarySearch methods (see the JavaDoc for Arrays.binarySearch(). + * In summary, if the value exists then the return value is the index of the existing value. + * If the value was not found, then the return value will be negative, and the + * place where the missing value should be inserted, can be determined by + * adding 1, and converting back to positive (or converting to positive, and subtracting 1). + * + */ + private static final int binarySearch(final Namespace[] data, + int left, int right, final Namespace key) { + // assume all input is valid. No need to waste time checking. + + // Because we are always searching inside of the scope array, and + // because there's always at least two scope members, we will always have + // a minimum value of 2 for 'right', and a maximum value of 1 for 'left' + // thus the following check is never needed. + // + // if (left >= right) { + // // we are searching in nothing, return the correct value + // // ... this is where IBM's JDK is broken - it just returns -1 + // return -left - 1; + // } + // make the right-side 'inclusive' instead of 'exclusive' + right--; + + while (left <= right) { + // get the mid-point. See the notes on the binary-search bug... + // ... not that we'll ever have that many Namspaces.... ;-) + // http://googleresearch.blogspot.com/2006/06/extra-extra-read-all-about-it-nearly.html + final int mid = (left + right) >>> 1; + if (data[mid] == key) { + // exact namespace match. + return mid; + } + final int cmp = NSCOMP.compare(data[mid], key); + + if (cmp < 0) { + left = mid + 1; + } else if (cmp > 0) { + right = mid - 1; + } else { + // Namespace prefix match. + return mid; + } + } + return -left - 1; + } + + /** The namespaces added to the scope at each depth */ + private Namespace[][] added = new Namespace[10][]; + /** The entire scope at each depth */ + private Namespace[][] scope = new Namespace[10][]; + /** The current depth */ + private int depth = -1; + + /** + * Create a NamespaceWalker ready to use as a stack. + *
+ * @see #push(Element) for comprehensive notes. + */ + public NamespaceStack() { + this(DEFAULTSEED); + } + + /** + * Create a NamespaceWalker ready to use as a stack. + *
+ * @see #push(Element) for comprehensive notes. + * @param seed The namespaces to set as the top level of the stack. + */ + public NamespaceStack(Namespace[] seed) { + depth++; + added[depth] = seed; + + scope[depth] = added[depth]; + } + + /** + * Inspect the scope array to see whether the namespace + * Namespace is 'new' or not. If it is 'new' then it is added to the + * store List. + * @param store Where to add the namespace if it is 'new' + * @param namespace The Namespace to check + * @param scope The array of Namespaces that are currently in-scope. + * @return The revised version of 'in-scope' if the scope has changed. If + * there is no modification then the same input scope will be returned. + */ + private static final Namespace[] checkNamespace(List store, + Namespace namespace, Namespace[] scope) { + // Scope is always sorted as the primary namespace first, then the + // rest are in prefix order. + // We can guarantee that the prefixes are all unique too. + // There is always going to be at least two namespaces in scope with + // the prefixes : "" and "xml" + // As a result we can use the 0th index with impunity. + if (namespace == scope[0]) { + // we are already in scope. + return scope; + } + if (namespace.getPrefix().equals(scope[0].getPrefix())) { + // the prefix is the previous scope's primary prefix. This means + // that we know for sure that the input namespace is new-to-scope. + store.add(namespace); + final Namespace[] nscope = ArrayCopy.copyOf(scope, scope.length); + nscope[0] = namespace; + return nscope; + } + // will return +ve number if the prefix matches too. + int ip = binarySearch(scope, 1, scope.length, namespace); + if (ip >= 0 && namespace == scope[ip]) { + // the namespace is already in scope. + return scope; + } + store.add(namespace); + if (ip >= 0) { + // a different namespace with the same prefix as us is in-scope. + // replace it.... + final Namespace[] nscope = ArrayCopy.copyOf(scope, scope.length); + nscope[ip] = namespace; + return nscope; + } + // We are a new prefix in-scope. + final Namespace[] nscope = ArrayCopy.copyOf(scope, scope.length + 1); + ip = - ip - 1; + System.arraycopy(nscope, ip, nscope, ip + 1, nscope.length - ip - 1); + nscope[ip] = namespace; + return nscope; + } + + /** + * Create a new in-scope level for the Stack based on an Element. + *
+ * The Namespaces associated with the input Element are used to modify the + * 'in-scope' Namespaces in this NamespaceStack. + *
+ * The following 'rules' will be applied: + *
    + *
  • Namespaces used in the input Element that were not part of the previous + * scope will be added to the new scope level in the stack. + *
  • If a new Namespace is added to the scope, but the previous scope + * already had a namespace with the same prefix, then that previous + * namespace is removed from the new scope (the new Namespace replaces + * the previous namespace with the same prefix). + *
  • The order of the in-scope Namespaces will always be: first the + * Namespace of the input Element followed by all other in-scope + * Namespaces sorted alphabetically by prefix. + *
  • The new in-scope Namespace values will be available in this class's + * iterator() method (which is available as part of this class's + * Iterable implementation. + *
  • The namespaces added to the scope by the input Element will be + * available in the {@link #addedForward()} Iterable. The order of + * the added Namespaces follows the same rules as above: first the + * Element Namespace (only if that Namespace is actually added) followed + * by the other added namespaces in alphabetical-by-prefix order. + *
  • The same added namespaces are also available in reverse order in + * the {@link #addedReverse()} Iterable. + *
+ * @param element The element at the new level of the stack. + */ + public void push(Element element) { + + // how many times do you add more than 8 namespaces in one go... + // we can add more if we need to... + final List toadd = new ArrayList(8); + final Namespace mns = element.getNamespace(); + // check to see whether the Namespace is new-to-scope. + Namespace[] newscope = checkNamespace(toadd, mns, scope[depth]); + if (element.hasAdditionalNamespaces()) { + for (final Namespace ns : element.getAdditionalNamespaces()) { + if (ns == mns) { + continue; + } + // check to see whether the Namespace is new-to-scope. + newscope = checkNamespace(toadd, ns, newscope); + } + } + if (element.hasAttributes()) { + for (final Attribute a : element.getAttributes()) { + final Namespace ns = a.getNamespace(); + if (ns == Namespace.NO_NAMESPACE) { + // Attributes are allowed to be in the NO_NAMESPACE without + // changing the in-scope set of the Element.... special-case + continue; + } + if (ns == mns) { + continue; + } + // check to see whether the Namespace is new-to-scope. + newscope = checkNamespace(toadd, ns, newscope); + } + } + + pushStack(mns, newscope, toadd); + + } + + /** + * Create a new in-scope level for the Stack based on an Attribute. + * + * @param att The attribute to contribute to the namespace scope. + */ + public void push(Attribute att) { + final List toadd = new ArrayList(1); + final Namespace mns = att.getNamespace(); + // check to see whether the Namespace is new-to-scope. + Namespace[] newscope = checkNamespace(toadd, mns, scope[depth]); + + pushStack(mns, newscope, toadd); + } + + private final void pushStack(final Namespace mns, Namespace[] newscope, + final List toadd) { + // OK, we've checked the namespaces in the Element, and 'toadd' contains + // all namespaces that are not already in scope. + depth++; + + if (depth >= scope.length) { + // we need more space on the stack. + scope = ArrayCopy.copyOf(scope, scope.length * 2); + added = ArrayCopy.copyOf(added, scope.length); + } + + // Sort out the added namespaces. + if (toadd.isEmpty()) { + // nothing changed in the scope. + added[depth] = EMPTY; + } else { + added[depth] = toadd.toArray(new Namespace[toadd.size()]); + if (added[depth][0] == mns) { + Arrays.sort(added[depth], 1, added[depth].length, NSCOMP); + } else { + Arrays.sort(added[depth], NSCOMP); + } + } + + if (mns != newscope[0]) { + if (toadd.isEmpty()) { + // we need to make newscope a copy of the previous level's + // scope, because it is not yet a copy. + newscope = ArrayCopy.copyOf(newscope, newscope.length); + } + // we need to take the Namespace at position 0, and insert it + // in it's place later in the array. + // we need to take the mns from later in the array, and move it + // to the front. + final Namespace tmp = newscope[0]; + int ip = - binarySearch(newscope, 1, newscope.length, tmp) - 1; + // we can be sure that (- ip - 1 ) is >= 1 + // we also know that we want to move the data before the ip + // backwards one spot, so the math is slightly different.... + ip--; + System.arraycopy(newscope, 1, newscope, 0, ip); + newscope[ip] = tmp; + + ip = binarySearch(newscope, 0, newscope.length, mns); + // we can be sure that ip is >= 0 + System.arraycopy(newscope, 0, newscope, 1, ip); + newscope[0] = mns; + } + + scope[depth] = newscope; + } + + /** + * Restore stack to the level prior to the current one. The various Iterator + * methods will thus return the data at the previous level. + */ + public void pop() { + if (depth <= 0) { + throw new IllegalStateException("Cannot over-pop the stack."); + } + scope[depth] = null; + added[depth] = null; + depth--; + } + + /** + * Return an Iterable containing all the Namespaces introduced to the + * current-level's scope. + * @see #push(Element) for the details on the data order. + * @return A read-only Iterable containing added Namespaces (may be empty); + */ + public Iterable addedForward() { + if (added[depth].length == 0) { + return EMPTYITER; + } + return new NamespaceIterable(added[depth], true); + } + + /** + * Return an Iterable containing all the Namespaces introduced to the + * current-level's scope but in reverse order to {@link #addedForward()}. + * @see #push(Element) for the details on the data order. + * @return A read-only Iterable containing added Namespaces (may be empty); + */ + public Iterable addedReverse() { + if (added[depth].length == 0) { + return EMPTYITER; + } + return new NamespaceIterable(added[depth], false); + } + + /** + * Get all the Namespaces in-scope at the current level of the stack. + * @see #push(Element) for the details on the data order. + * @return A read-only Iterator containing added Namespaces (may be empty); + */ + @Override + public Iterator iterator() { + return new ForwardWalker(scope[depth]); + } + + /** + * Return a new array instance representing the current scope. + * Modifying the returned array will not affect this scope. + * @return a copy of the current scope. + */ + public Namespace[] getScope() { + return ArrayCopy.copyOf(scope[depth], scope[depth].length); + } + + /** + * Inspect the current scope and return true if the specified namespace is + * in scope. + * @param ns The Namespace to check + * @return true if the current scope contains that Namespace. + */ + public boolean isInScope(Namespace ns) { + if (ns == scope[depth][0]) { + return true; + } + final int ip = binarySearch(scope[depth], 1, scope[depth].length, ns); + if (ip >= 0) { + // we have the same prefix. + return ns == scope[depth][ip]; + } + return false; + } + +} diff --git a/core/src/java/org/jdom/util/package.html b/core/src/java/org/jdom/util/package.html new file mode 100644 index 0000000..6583146 --- /dev/null +++ b/core/src/java/org/jdom/util/package.html @@ -0,0 +1,3 @@ + + Classes that implement useful functionality, but are not easy to categorise. + diff --git a/core/src/java/org/jdom/xpath/XPath.java b/core/src/java/org/jdom/xpath/XPath.java new file mode 100644 index 0000000..0f8cb54 --- /dev/null +++ b/core/src/java/org/jdom/xpath/XPath.java @@ -0,0 +1,466 @@ +/*-- + + Copyright (C) 2000-2007 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath; + + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; + +import org.jdom.*; +import org.jdom.internal.SystemProperty; + + +/** + * A utility class for performing XPath calls on JDOM nodes, with a factory + * interface for obtaining a first XPath instance. Users operate against this + * class while XPath vendors can plug-in implementations underneath. Users + * can choose an implementation using either {@link #setXPathClass} or + * the system property "org.jdom.xpath.class". + * + * @author Laurent Bihanic + * @deprecated Use XPathFactory/XPathExpression/XPathBuilder instead. + */ +@Deprecated +public abstract class XPath implements Serializable { + + /** + * Standard JDOM2 Serialization. Default mechanism. + */ + private static final long serialVersionUID = 200L; + + /** + * The name of the system property from which to retrieve the + * name of the implementation class to use. + *

+ * The property name is: + * "org.jdom.xpath.class".

+ */ + private final static String XPATH_CLASS_PROPERTY = "org.jdom.xpath.class"; + + /** + * The default implementation class to use if none was configured. + */ + private final static String DEFAULT_XPATH_CLASS = + "org.jdom.xpath.jaxen.JDOMXPath"; + + /** + * The string passable to the JAXP 1.3 XPathFactory isObjectModelSupported() + * method to query an XPath engine regarding its support for JDOM. Defined + * to be the well-known URI "http://jdom.org/jaxp/xpath/jdom". + */ + public final static String JDOM_OBJECT_MODEL_URI = + "http://jdom.org/jaxp/xpath/jdom"; + + /** + * The constructor to instanciate a new XPath concrete + * implementation. + * + * @see #newInstance + */ + private static Constructor constructor = null; + + /** + * Creates a new XPath wrapper object, compiling the specified + * XPath expression. + * + * @param path the XPath expression to wrap. + * @return an XPath instance representing the input path + * + * @throws JDOMException if the XPath expression is invalid. + */ + public static XPath newInstance(String path) throws JDOMException { + try { + if (constructor == null) { + // First call => Determine implementation. + String className; + try { + className = SystemProperty.get(XPATH_CLASS_PROPERTY, + DEFAULT_XPATH_CLASS); + } + catch (SecurityException ex1) { + // Access to system property denied. => Use default impl. + className = DEFAULT_XPATH_CLASS; + } + @SuppressWarnings("unchecked") + Class useclass = (Class) Class.forName(className); + if (!XPath.class.isAssignableFrom(useclass)) { + throw new JDOMException("Unable to create a JDOMXPath from class '" + className + "'."); + } + setXPathClass(useclass); + } + // Allocate and return new implementation instance. + return constructor.newInstance(path); + } + catch (JDOMException ex1) { + throw ex1; + } + catch (InvocationTargetException ex2) { + // Constructor threw an error on invocation. + Throwable t = ex2.getTargetException(); + + throw (t instanceof JDOMException)? (JDOMException)t: + new JDOMException(t.toString(), t); + } + catch (Exception ex3) { + // Any reflection error (probably due to a configuration mistake). + throw new JDOMException(ex3.toString(), ex3); + } + } + + /** + * Sets the concrete XPath subclass to use when allocating XPath + * instances. + * + * @param aClass the concrete subclass of XPath. + * + * @throws IllegalArgumentException if aClass is + * null. + * @throws JDOMException if aClass is + * not a concrete subclass + * of XPath. + */ + public static void setXPathClass(Class aClass) throws JDOMException { + if (aClass == null) { + throw new IllegalArgumentException("aClass"); + } + + try { + if ((XPath.class.isAssignableFrom(aClass)) && + (Modifier.isAbstract(aClass.getModifiers()) == false)) { + // Concrete subclass of XPath => Get constructor + constructor = aClass.getConstructor(String.class); + } + else { + throw new JDOMException(aClass.getName() + + " is not a concrete JDOM XPath implementation"); + } + } + catch (JDOMException ex1) { + throw ex1; + } + catch (Exception ex2) { + // Any reflection error (probably due to a configuration mistake). + throw new JDOMException(ex2.toString(), ex2); + } + } + + /** + * Evaluates the wrapped XPath expression and returns the list + * of selected items. + * + * @param context the node to use as context for evaluating + * the XPath expression. + * + * @return the list of selected items, which may be of types: {@link Element}, + * {@link Attribute}, {@link Text}, {@link CDATA}, + * {@link Comment}, {@link ProcessingInstruction}, Boolean, + * Double, or String. + * + * @throws JDOMException if the evaluation of the XPath + * expression on the specified context + * failed. + */ + abstract public List selectNodes(Object context) throws JDOMException; + + /** + * Evaluates the wrapped XPath expression and returns the first + * entry in the list of selected nodes (or atomics). + * + * @param context the node to use as context for evaluating + * the XPath expression. + * + * @return the first selected item, which may be of types: {@link Element}, + * {@link Attribute}, {@link Text}, {@link CDATA}, + * {@link Comment}, {@link ProcessingInstruction}, Boolean, + * Double, String, or null if no item was selected. + * + * @throws JDOMException if the evaluation of the XPath + * expression on the specified context + * failed. + */ + abstract public Object selectSingleNode(Object context) throws JDOMException; + + /** + * Returns the string value of the first node selected by applying + * the wrapped XPath expression to the given context. + * + * @param context the element to use as context for evaluating + * the XPath expression. + * + * @return the string value of the first node selected by applying + * the wrapped XPath expression to the given context. + * + * @throws JDOMException if the XPath expression is invalid or + * its evaluation on the specified context + * failed. + */ + abstract public String valueOf(Object context) throws JDOMException; + + /** + * Returns the number value of the first node selected by applying + * the wrapped XPath expression to the given context. + * + * @param context the element to use as context for evaluating + * the XPath expression. + * + * @return the number value of the first node selected by applying + * the wrapped XPath expression to the given context, + * null if no node was selected or the + * special value {@link java.lang.Double#NaN} + * (Not-a-Number) if the selected value can not be + * converted into a number value. + * + * @throws JDOMException if the XPath expression is invalid or + * its evaluation on the specified context + * failed. + */ + abstract public Number numberValueOf(Object context) throws JDOMException; + + /** + * Defines an XPath variable and sets its value. + * + * @param name the variable name. + * @param value the variable value. + * + * @throws IllegalArgumentException if name is not + * a valid XPath variable name + * or if the value type is not + * supported by the underlying + * implementation + */ + abstract public void setVariable(String name, Object value); + + /** + * Adds a namespace definition to the list of namespaces known of + * this XPath expression. + *

+ * Note: In XPath, there is no such thing as a + * 'default namespace'. The empty prefix always resolves + * to the empty namespace URI.

+ * + * @param namespace the namespace. + */ + abstract public void addNamespace(Namespace namespace); + + /** + * Adds a namespace definition (prefix and URI) to the list of + * namespaces known of this XPath expression. + *

+ * Note: In XPath, there is no such thing as a + * 'default namespace'. The empty prefix always resolves + * to the empty namespace URI.

+ * + * @param prefix the namespace prefix. + * @param uri the namespace URI. + * + * @throws IllegalNameException if the prefix or uri are null or + * empty strings or if they contain + * illegal characters. + */ + public void addNamespace(String prefix, String uri) { + addNamespace(Namespace.getNamespace(prefix, uri)); + } + + /** + * Returns the wrapped XPath expression as a string. + * + * @return the wrapped XPath expression as a string. + */ + abstract public String getXPath(); + + + /** + * Evaluates an XPath expression and returns the list of selected + * items. + *

+ * Note: This method should not be used when the + * same XPath expression needs to be applied several times (on the + * same or different contexts) as it requires the expression to be + * compiled before being evaluated. In such cases, + * {@link #newInstance allocating} an XPath wrapper instance and + * {@link #selectNodes(java.lang.Object) evaluating} it several + * times is way more efficient. + *

+ * + * @param context the node to use as context for evaluating + * the XPath expression. + * @param path the XPath expression to evaluate. + * + * @return the list of selected items, which may be of types: {@link Element}, + * {@link Attribute}, {@link Text}, {@link CDATA}, + * {@link Comment}, {@link ProcessingInstruction}, Boolean, + * Double, or String. + * + * @throws JDOMException if the XPath expression is invalid or + * its evaluation on the specified context + * failed. + */ + public static List selectNodes(Object context, String path) + throws JDOMException { + return newInstance(path).selectNodes(context); + } + + /** + * Evaluates the wrapped XPath expression and returns the first + * entry in the list of selected nodes (or atomics). + *

+ * Note: This method should not be used when the + * same XPath expression needs to be applied several times (on the + * same or different contexts) as it requires the expression to be + * compiled before being evaluated. In such cases, + * {@link #newInstance allocating} an XPath wrapper instance and + * {@link #selectSingleNode(java.lang.Object) evaluating} it + * several times is way more efficient. + *

+ * + * @param context the element to use as context for evaluating + * the XPath expression. + * @param path the XPath expression to evaluate. + * + * @return the first selected item, which may be of types: {@link Element}, + * {@link Attribute}, {@link Text}, {@link CDATA}, + * {@link Comment}, {@link ProcessingInstruction}, Boolean, + * Double, String, or null if no item was selected. + * + * @throws JDOMException if the XPath expression is invalid or + * its evaluation on the specified context + * failed. + */ + public static Object selectSingleNode(Object context, String path) + throws JDOMException { + return newInstance(path).selectSingleNode(context); + } + + + //------------------------------------------------------------------------- + // Serialization support + //------------------------------------------------------------------------- + + /** + * [Serialization support] Returns the alternative object + * to write to the stream when serializing this object. This + * method returns an instance of a dedicated nested class to + * serialize XPath expressions independently of the concrete + * implementation being used. + *

+ * Note: Subclasses are not allowed to override + * this method to ensure valid serialization of all + * implementations.

+ * + * @return an XPathString instance configured with the wrapped + * XPath expression. + * + * @throws ObjectStreamException never. + */ + protected final Object writeReplace() throws ObjectStreamException { + return new XPathString(this.getXPath()); + } + + /** + * The XPathString is dedicated to serialize instances of + * XPath subclasses in a implementation-independent manner. + *

+ * XPathString ensures that only string data are serialized. Upon + * deserialization, XPathString relies on XPath factory method to + * to create instances of the concrete XPath wrapper currently + * configured.

+ */ + private final static class XPathString implements Serializable { + /** + * Standard JDOM2 Serialization. Default mechanism. + */ + private static final long serialVersionUID = 200L; + + /** + * The XPath expression as a string. + */ + private String xPath = null; + + /** + * Creates a new XPathString instance from the specified + * XPath expression. + * + * @param xpath the XPath expression. + */ + public XPathString(String xpath) { + super(); + + this.xPath = xpath; + } + + /** + * [Serialization support] Resolves the read XPathString + * objects into XPath implementations. + * + * @return an instance of a concrete implementation of + * XPath. + * + * @throws ObjectStreamException if no XPath could be built + * from the read object. + */ + private Object readResolve() throws ObjectStreamException { + try { + return XPath.newInstance(this.xPath); + } + catch (JDOMException ex1) { + throw new InvalidObjectException( + "Can't create XPath object for expression \"" + + this.xPath + "\": " + ex1.toString()); + } + } + } +} + diff --git a/core/src/java/org/jdom/xpath/XPathBuilder.java b/core/src/java/org/jdom/xpath/XPathBuilder.java new file mode 100644 index 0000000..7291795 --- /dev/null +++ b/core/src/java/org/jdom/xpath/XPathBuilder.java @@ -0,0 +1,300 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.jdom.Namespace; +import org.jdom.filter2.Filter; + +/** + * A helper class for creating {@link XPathExpression} instances without having + * to manage your own Namespace and Variable contexts. + * + * @author Rolf Lear + * @param + * The generic type of the returned results. + */ +public class XPathBuilder { + private final Filter filter; + private final String expression; + private Map variables; + private Map namespaces; + + /** + * Create a skeleton XPathBuilder with the given expression and result + * filter. + * + * @param expression + * The XPath expression. + * @param filter + * The Filter to perform the coercion with. + */ + public XPathBuilder(String expression, Filter filter) { + if (expression == null) { + throw new NullPointerException("Null expression"); + } + if (filter == null) { + throw new NullPointerException("Null filter"); + } + this.filter = filter; + this.expression = expression; + } + + /** + * Define or redefine an XPath expression variable value. In XPath, variable + * names can be in a namespace, and thus the variable name is in a QName + * form: prefix:name + *

+ * Variables without a prefix are in the "" Namespace. + *

+ * Variables can have a null value. The {@link XPathExpression} can change + * the variable value before the expression is evaluated, and, some XPath + * libraries support a null variable value. See + * {@link XPathExpression#setVariable(String, Namespace, Object)}. + *

+ * In order to validate that a Variable is unique you have to know the + * namespace associated with the prefix. This class is designed to make it + * possible to make the namespace associations after the variables have been + * added. As a result it is not possible to validate the uniqueness of a + * variable name until the {@link #compileWith(XPathFactory)} method is + * called. + *

+ * As a consequence of the above, this class assumes that each unique prefix + * is for a unique Namespace URI (thus calling this method with different + * QNames is assumed to be setting different variables). This may lead to an + * IllegalArgumentException when the {@link #compileWith(XPathFactory)} + * method is called. + *

+ * This method does not validate the format of the variable name either, it + * instead postpones the validation until the expression is compiled. As a + * result you may encounter IllegalArgumentExceptions at compile time if the + * variable names are not valid XPath QNames ("name" or "prefix:name"). + * + * @param qname + * The variable name to define. + * @param value + * The variable value to set. + * @return true if this variable was defined, false if it was previously + * defined and has now been redefined. + * @throws NullPointerException + * if the name is null. + */ + public boolean setVariable(String qname, Object value) { + if (qname == null) { + throw new NullPointerException("Null variable name"); + } + if (variables == null) { + variables = new HashMap(); + } + return variables.put(qname, value) == null; + } + + /** + * Define a Namespace to be available for the XPath expression. If a + * Namespace with the same prefix was previously defined then the prefix + * will be re-defined. + * + * @param prefix + * The namespace prefix to define. + * @param uri + * The namespace URI to define. + * @return true if the Namespace prefix was newly defined, false if the + * prefix was previously defined and has now been redefined. + */ + public boolean setNamespace(String prefix, String uri) { + if (prefix == null) { + throw new NullPointerException("Null prefix"); + } + if (uri == null) { + throw new NullPointerException("Null URI"); + } + return setNamespace(Namespace.getNamespace(prefix, uri)); + } + + /** + * Define a Namespace to be available for the XPath expression. + * + * @param namespace + * The namespace to define. + * @return true if this Namespace prefix was newly defined, false if the + * prefix was previously defined and has now been redefined. + * @throws NullPointerException + * if the namespace is null. + */ + public boolean setNamespace(Namespace namespace) { + if (namespace == null) { + throw new NullPointerException("Null Namespace"); + } + if ("".equals(namespace.getPrefix())) { + if (Namespace.NO_NAMESPACE != namespace) { + throw new IllegalArgumentException( + "Cannot set a Namespace URI in XPath for the \"\" prefix."); + } + // NO_NAMESPACE is always defined... + return false; + } + + if (namespaces == null) { + namespaces = new HashMap(); + } + return namespaces.put(namespace.getPrefix(), namespace) == null; + } + + /** + * Add a number of namespaces to this XPathBuilder + * + * @param namespaces + * The namespaces to set. + * @return true if any of the Namespace prefixes are new to the XPathBuilder + * @throws NullPointerException + * if the namespace collection, or any of its members are null. + */ + public boolean setNamespaces(Collection namespaces) { + if (namespaces == null) { + throw new NullPointerException("Null namespaces Collection"); + } + boolean ret = false; + for (Namespace ns : namespaces) { + if (setNamespace(ns)) { + ret = true; + } + } + return ret; + } + + /** + * Get the variable value associated with the given name. See + * {@link #setVariable(String, Object)} for notes on how the Namespaces need + * to be established before an authoritative reference to a variable can be + * made. As a result, this method uses the simple variable QName name to + * reference the variable. + * + * @param qname + * The variable name to get the vaiable value for. + * @return the variable value, or null if the variable was not defined. + * @throws NullPointerException + * if the qname is null. + */ + public Object getVariable(String qname) { + if (qname == null) { + throw new NullPointerException("Null qname"); + } + if (variables == null) { + return null; + } + return variables.get(qname); + } + + /** + * Get the Namespace associated with the given prefix. + * + * @param prefix + * The Namespace prefix to get the Namespace for. + * @return the Namespace with that prefix, or null if that prefix was never + * defined. + */ + public Namespace getNamespace(String prefix) { + if (prefix == null) { + throw new NullPointerException("Null prefix"); + } + if ("".equals(prefix)) { + return Namespace.NO_NAMESPACE; + } + if (namespaces == null) { + return null; + } + return namespaces.get(prefix); + } + + /** + * Get the Filter instance used for coercion. + * + * @return the coercion Filter. + */ + public Filter getFilter() { + return filter; + } + + /** + * Get the XPath expression. + * + * @return the XPath expression. + */ + public String getExpression() { + return expression; + } + + /** + * Compile an XPathExpression using the details currently stored in the + * XPathBuilder. + * + * @param factory + * The XPath factory to use for compiling. + * @return The compiled XPath expression + * @throws IllegalArgumentException + * if the expression cannot be compiled. + */ + public XPathExpression compileWith(XPathFactory factory) { + if (namespaces == null) { + return factory.compile(expression, filter, variables); + } + return factory.compile(expression, filter, variables, namespaces + .values().toArray(new Namespace[namespaces.size()])); + } + +} diff --git a/core/src/java/org/jdom/xpath/XPathDiagnostic.java b/core/src/java/org/jdom/xpath/XPathDiagnostic.java new file mode 100644 index 0000000..33c4749 --- /dev/null +++ b/core/src/java/org/jdom/xpath/XPathDiagnostic.java @@ -0,0 +1,114 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath; + +import java.util.List; + +/** + * Class representing the results of an XPath query allowing JDOM users to trace + * whether an item returned from an XPath query is subsequently filtered by the + * coercion filter attached to the {@link XPathExpression}; + * + * @author Rolf Lear + * @param + * The generic type of the results retruend by the expression. + */ +public interface XPathDiagnostic { + /** + * @return The context object against which the XPath query was evaluated. + */ + public Object getContext(); + + /** + * @return the {@link XPathExpression} instance that generated this + * diagnostic. + */ + public XPathExpression getXPathExpression(); + + /** + * Returns the results as they would be returned by the regular evaluate + * process (read-only). + * + * @return the regular evaluated results. + */ + public List getResult(); + + /** + * Returns the XPath results which are not returned by the regular evaluate + * process. + * + * @return those results which were returned by the XPath query but were + * filtered out by the JDOM Filter. + */ + public List getFilteredResults(); + + /** + * Returns the XPath results before any were filtered. + * + * @return those results which were returned by the XPath query before any + * filtering. + */ + public List getRawResults(); + + /** + * Indicate whether the query was evaluated as a first-only evaluation. + * XPath libraries are allowed to stop processing the results after the + * first result is retrieved if first-only processing is set. + * + * @return true if the evaluation was a first-only evaluation. + */ + public boolean isFirstOnly(); + +} diff --git a/core/src/java/org/jdom/xpath/XPathExpression.java b/core/src/java/org/jdom/xpath/XPathExpression.java new file mode 100644 index 0000000..29798a0 --- /dev/null +++ b/core/src/java/org/jdom/xpath/XPathExpression.java @@ -0,0 +1,287 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath; + +import java.util.List; + +import org.jdom.Namespace; +import org.jdom.filter2.Filter; + +/** + * XPathExpression is a representation of a compiled XPath query and any + * Namespace or variable references the query may require. + *

+ * Once an XPathExpression is created, the values associated with variable names + * can be changed. But new variables may not be added. + *

+ *

+ * XPathExpression is not thread-safe. XPath libraries allow variable values to + * change between calls to their query routines, but require that the variable + * value is constant for the duration of any particular evaluation. It is easier + * to simply have separate XPathExpression instances in each thread than it is + * to manage the synchronization of a single instance. XPathExpression thus + * supports Cloneable to easily create another XPathExpression instance. It is + * the responsibility of the JDOM caller to ensure appropriate synchronisation + * of the XPathExpression if it is accessed from multiple threads. + * + * @author Rolf Lear + * @param + * The generic type of the results of the XPath query after being + * processed by the JDOM {@code Filter} + */ +public interface XPathExpression extends Cloneable { + + /** + * Create a new instance of this XPathExpression that duplicates this + * instance. + *

+ * The 'cloned' instance will have the same XPath query, namespace + * declarations, and variables. Changing a value associated with a variable + * on the cloned instance will not change this instance's values, and it is + * safe to run the evaluate methods on the cloned copy at the same time as + * this copy. + * + * @return a new XPathExpression instance that shares the same core details + * as this. + */ + public XPathExpression clone(); + + /** + * Get the XPath expression + * + * @return the string representation of the XPath expression + */ + public String getExpression(); + + /** + * Get the Namespace associated with a given prefix. + * + * @param prefix + * The prefix to select the Namespace URI for. + * @return the URI of the specified Namespace prefix + * @throws IllegalArgumentException + * if that prefix is not defined. + */ + public Namespace getNamespace(String prefix); + + /** + * Get the Namespaces that were used to compile this XPathExpression. + * + * @return a potentially empty array of Namespaces (never null). + */ + public Namespace[] getNamespaces(); + + /** + * Change the defined value for a variable to some new value. You may not + * use this method to add new variables to the compiled XPath, you can only + * change existing variable values. + *

+ * The value of the variable may be null. Some XPath libraries support a + * null value, and if the library that this expression is for does not + * support a null value it should be translated to something meaningful for + * that library, typically the empty string. + * + * @param localname + * The variable localname to change. + * @param uri + * the Namespace in which the variable name is declared. + * @param value + * The new value to set. + * @return The value of the variable prior to this change. + * @throws NullPointerException + * if name or uri is null + * @throws IllegalArgumentException + * if name is not already a variable. + */ + public Object setVariable(String localname, Namespace uri, Object value); + + /** + * Change the defined value for a variable to some new value. You may not + * use this method to add new variables to the compiled XPath, you can only + * change existing variable values. + *

+ * The value of the variable may be null. Some XPath libraries support a + * null value, and if the library that this expression is for does not + * support a null value it should be translated to something meaningful for + * that library, typically the empty string. + *

+ * qname must consist of an optional namespace prefix and colon, followed + * by a mandatory variable localname. If the prefix is not specified, then + * the Namespace is assumed to be the {@link Namespace#NO_NAMESPACE}. If + * the prefix is specified, it must match with one of the declared + * Namespaces for this XPathExpression + * + * @param qname + * The variable qname to change. + * @param value + * The new value to set. + * @return The value of the variable prior to this change. + * @throws NullPointerException + * if qname is null + * @throws IllegalArgumentException + * if name is not already a variable. + */ + public Object setVariable(String qname, Object value); + + /** + * Get the variable value associated to the given variable name. + * + * @param localname + * the variable localname to retrieve the value for. + * @param uri + * the Namespace in which the variable name was declared. + * @return the value associated to a Variable name. + * @throws NullPointerException + * if name or uri is null + * @throws IllegalArgumentException + * if that variable name is not defined. + */ + public Object getVariable(String localname, Namespace uri); + + /** + * Get the variable value associated to the given variable qname. + *

+ * qname must consist of an optional namespace prefix and colon, followed + * by a mandatory variable localname. If the prefix is not specified, then + * the Namespace is assumed to be the {@link Namespace#NO_NAMESPACE}. If + * the prefix is specified, it must match with one of the declared + * Namespaces for this XPathExpression + * + * @param qname + * the variable qname to retrieve the value for. + * @return the value associated to a Variable name. + * @throws NullPointerException + * if qname is null + * @throws IllegalArgumentException + * if that variable name is not defined. + */ + public Object getVariable(String qname); + + /** + * Get the {@code Filter} used to coerce the raw XPath results in to + * the correct Generic type. + * @return the {@code Filter} used to coerce the raw XPath results in to + * the correct Generic type. + */ + public Filter getFilter(); + + /** + * Process the compiled XPathExpression against the specified context. + *

+ * In the JDOM2 XPath API the results of the raw XPath query are processed + * by the attached {@code Filter} instance to coerce the results in to + * the correct generic type for this XPathExpression. The Filter process may + * cause some XPath results to be removed from the final results. You may + * instead want to call the {@link #diagnose(Object, boolean)} method to + * have access to both the raw XPath results as well as the filtered and + * generically typed results. + * + * @param context + * The context against which to process the query. + * @return a list of the XPath results. + * @throws NullPointerException + * if the context is null + * @throws IllegalStateException + * if the expression is not runnable or if the context node is not + * appropriate for the expression. + */ + public List evaluate(Object context); + + /** + * Return the first value in the XPath query result set type-cast to the + * return type of this XPathExpression. + *

+ * The concept of the 'first' result is applied before any JDOM Filter is + * applied. Thus, if the underlying XPath query has some results, the first + * result is sent through the filter. If it matches it is returned, if it + * does not match, then null is returned (even if some subsequent result + * underlying XPath result would pass the filter). + *

+ * This allows the XPath implementation to optimise the evaluateFirst method + * by potentially using 'short-circuit' conditions in the evaluation. + *

+ * + * @param context + * The context against which to evaluate the expression. This will + * typically be a Document, Element, or some other JDOM object. + * @return The first XPath result (if there is any) coerced to the generic + * type of this XPathExpression, or null if it cannot be coerced. + * @throws NullPointerException + * if the context is null + * @throws IllegalStateException + * if the expression is not runnable or if the context node is not + * appropriate for the expression. + */ + public T evaluateFirst(Object context); + + /** + * Evaluate the XPath query against the supplied context, but return + * additional data which may be useful for diagnosing problems with XPath + * queries. + * + * @param context + * The context against which to run the query. + * @param firstonly + * Indicate whether the XPath expression can be terminated after the + * first successful result value. + * @return an {@link XPathDiagnostic} instance. + * @throws NullPointerException + * if the context is null + * @throws IllegalStateException + * if the expression is not runnable or if the context node is not + * appropriate for the expression. + */ + public XPathDiagnostic diagnose(Object context, boolean firstonly); +} diff --git a/core/src/java/org/jdom/xpath/XPathFactory.java b/core/src/java/org/jdom/xpath/XPathFactory.java new file mode 100644 index 0000000..86720d7 --- /dev/null +++ b/core/src/java/org/jdom/xpath/XPathFactory.java @@ -0,0 +1,311 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import org.jdom.JDOMConstants; +import org.jdom.Namespace; +import org.jdom.filter2.Filter; +import org.jdom.filter2.Filters; +import org.jdom.internal.ReflectionConstructor; +import org.jdom.internal.SystemProperty; +import org.jdom.xpath.jaxen.JaxenXPathFactory; + +/** + * XPathFactory allows JDOM users to configure which XPath implementation to use + * when evaluating XPath expressions. + *

+ * JDOM does not extend the core Java XPath API (javax.xml.xpath.XPath). Instead + * it creates a new API that is more JDOM and Java friendly leading to neater + * and more understandable code (in a JDOM context). + *

+ * A JDOM XPathFactory instance is able to create JDOM XPathExpression instances + * that can be used to evaluate XPath expressions against JDOM Content. + *

+ * The XPathFactory allows either default or custom XPathFactory instances to be + * created. If you use the {@link #newInstance(String)} method then an + * XPathFactory of that specific type will be created. If you use the + * {@link #instance()} method then a default XPathFactory instance will be + * returned. + *

+ * Instances of XPathFactory are specified to be thread-safe. You can reuse an + * XPathFactory in multiple threads. Instances of XPathExpression are + * NOT thread-safe. + * + * @since JDOM2 + * @author Rolf Lear + */ +public abstract class XPathFactory { + + private static final Namespace[] EMPTYNS = new Namespace[0]; + + /** + * An atomic reference storing an instance of the default XPathFactory. + */ + private static final AtomicReference defaultreference = new AtomicReference(); + + private static final String DEFAULTFACTORY = SystemProperty.get( + JDOMConstants.JDOM2_PROPERTY_XPATH_FACTORY, null); + + /** + * Obtain an instance of an XPathFactory using the default mechanisms to + * determine what XPathFactory implementation to use. + *

+ * The exact same XPathFactory instance will be returned from each call. + *

+ * The default mechanism will inspect the system property (only once) + * {@link JDOMConstants#JDOM2_PROPERTY_XPATH_FACTORY} to determine what + * class should be used for the XPathFactory. If that property is not set + * then JDOM will use the {@link JaxenXPathFactory}. + * + * @return the default XPathFactory instance + */ + public static final XPathFactory instance() { + final XPathFactory ret = defaultreference.get(); + if (ret != null) { + return ret; + } + XPathFactory fac = DEFAULTFACTORY == null ? new JaxenXPathFactory() + : newInstance(DEFAULTFACTORY); + if (defaultreference.compareAndSet(null, fac)) { + return fac; + } + // someone else installed a different instance before we added ours. + // return that other instance. + return defaultreference.get(); + } + + /** + * Create a new instance of an explicit XPathFactory. A new instance of the + * specified XPathFactory is created each time. The target XPathFactory + * needs to have a no-argument default constructor. + *

+ * This method is a convenience mechanism only, and JDOM users are free to + * create a custom XPathFactory instance and use a simple:
+ * XPathFactory fac = new MyXPathFactory(arg1, arg2, ...) + * + * @param factoryclass + * The name of the XPathFactory class to create. + * @return An XPathFactory of the specified class. + */ + public static final XPathFactory newInstance(String factoryclass) { + return ReflectionConstructor + .construct(factoryclass, XPathFactory.class); + } + + /** + * Create a Compiled XPathExpression<> instance from this factory. This + * is the only abstract method on this class. All other compile and evaluate + * methods prepare the data in some way to call this compile method. + *

+ * XPathFactory implementations override this method to implement support + * for the JDOM/XPath API. + *

+ * A Filter is used to coerce resulting XPath data in to a suitable JDOM generic + * type. Note that the {@link Filters} class has a number of predefined, useful + * filters. + *

+ *

Namespace

XPath expressions are always namespace aware, and + * expect to be able to resolve prefixes to namespace URIs. In XPath + * expressions the prefix "" always resolves to the empty Namespace URI "". + * A prefix in an XPath query is expected to resolve to exactly one URI. + * Multiple different prefixes in the expression may resolve to the same + * URI. + *

+ * This compile method ensures that these XPath/Namespace rules are followed + * and thus this method will throw IllegalArgumentException if: + *

    + *
  • a namespace has the empty-string prefix but has a non-empty URI. + *
  • more than one Namespace has any one prefix. + *
+ *

+ *

Variables

+ *

+ * Variables are referenced from XPath expressions using a + * $varname syntax. The variable name may be a Namespace + * qualified variable name of the form $pfx:localname. + * Variables $pa:var and $pb:var are the identical + * variables if the namespace URI for prefix 'pa' is the same URI as for + * prefix 'pb'. + *

+ * This compile method expects all variable names to be expressed in a + * prefix-qualified format, where all prefixes have to be available in one + * of the specified Namespace instances. + *

+ * e.g. if you specify a variable name "ns:var" with value "value", you also + * need to have some namespace provided with the prefix "ns" such as + * Namespace.getNamespace("ns", "http://example.com/nsuri"); + *

+ * Some XPath libraries allow null variable values (Jaxen), some do not + * (native Java). This compile method will silently convert any null + * Variable value to an empty string "". + *

+ * Variables are provided in the form of a Map where the key is the variable + * name and the mapped value is the variable value. If the entire map is + * null then the compile Method assumes there are no variables. + *

+ * In light of the above, this compile method will throw an + * IllegalArgumentException if: + *

    + *
  • a variable name is not a valid XML QName. + *
  • The prefix associated with a variable name is not available as a + * Namespace. + *
+ * A NullPointerException will be thrown if the map contains a null variable + * name + * + * @param + * The generic type of the results that the XPathExpression will + * produce. + * @param expression + * The XPath expression. + * @param filter + * The Filter that is used to coerce the XPath result data in to the + * generic-typed results. + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @param variables + * Any variable values that may be referenced from the query. A null + * value indicates that there are no variables. + * @param namespaces + * Any namespaces that may be referenced from the query + * @return an XPathExpression<> instance. + * @throws NullPointerException + * if the query, filter, any namespace, any variable name or any + * variable value is null (although the entire variables value may + * be null). + * @throws IllegalArgumentException + * if any two Namespace values share the same prefix, or if there is + * any other reason that the XPath query cannot be compiled. + * @see org.jdom.filter2.Filters + */ + public abstract XPathExpression compile(String expression, + Filter filter, Map variables, + Namespace... namespaces); + + /** + * Create a XPathExpression<> instance from this factory. + * + * @param + * The generic type of the results that the XPathExpression will + * produce. + * @param expression + * The XPath expression. + * @param filter + * The Filter that is used to coerce the xpath result data in to the + * generic-typed results. + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @param variables + * Any variable values that may be referenced from the query. A null + * value indicates that there are no variables. + * @param namespaces + * List of all namespaces that may be referenced from the query + * @return an XPathExpression<T> instance. + * @throws NullPointerException + * if the query, filter, namespaces, any variable name or any + * variable value is null (although the entire variables value may + * be null). + * @throws IllegalArgumentException + * if any two Namespace values share the same prefix, or if there is + * any other reason that the XPath query cannot be compiled. + */ + public XPathExpression compile(String expression, Filter filter, + Map variables, Collection namespaces) { + return compile(expression, filter, variables, namespaces.toArray(EMPTYNS)); + } + + /** + * Create a XPathExpression<T> instance from this factory. + * + * @param + * The generic type of the results that the XPathExpression will + * produce. + * @param expression + * The XPath expression. + * @param filter + * The Filter that is used to coerce the xpath result data in to the + * generic-typed results. + * Note that the {@link Filters} class has a number of predefined, useful + * filters. + * @return an XPathExpression<T> instance. + * @throws NullPointerException + * if the query or filter is null + * @throws IllegalArgumentException + * if there is any reason that the XPath query cannot be compiled. + */ + public XPathExpression compile(String expression, Filter filter) { + return compile(expression, filter, null, EMPTYNS); + } + + /** + * Create a XPathExpression<Object> instance from this factory. + * + * @param expression + * The XPath expression. + * @return an XPathExpression<Object> instance. + * @throws NullPointerException + * if the query or filter is null + * @throws IllegalArgumentException + * if there is any reason that the XPath query cannot be compiled. + */ + public XPathExpression compile(String expression) { + return compile(expression, Filters.fpassthrough(), null, EMPTYNS); + } + +} diff --git a/core/src/java/org/jdom/xpath/XPathHelper.java b/core/src/java/org/jdom/xpath/XPathHelper.java new file mode 100644 index 0000000..30226ae --- /dev/null +++ b/core/src/java/org/jdom/xpath/XPathHelper.java @@ -0,0 +1,575 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.NamespaceAware; +import org.jdom.Parent; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter2.AbstractFilter; +import org.jdom.filter2.Filters; + +/** + * Provides a set of utility methods to generate XPath expressions to select a + * given node in a document. You can generate absolute XPath expressions to the + * target node, or relative expressions from a specified start point. If you + * request a relative expression, the start and target nodes must share some + * common ancestry. + *

+ * XPaths are required to be namespace-aware. Typically this is done by using + * a namespace-prefixed query, with the actual namespace URI being set in the + * context of the XPath expression. This is not possible to express using a + * simple String return value. As a work-around, this method uses a potentially + * slower, but more reliable, mechanism for ensuring the correct namespace + * context is selected. The mechanism will appear like (for Elements): + *
+ * .../*[local-name() = 'tag' and namespace-uri() = 'uri'] + *
+ * Similarly, Attributes will have a syntax similar to: + *
+ * .../@*[local-name() = 'attname' and namespace-uri() = 'uri'] + *
+ * This mechanism makes it possible to have a simple namespace context, and a + * simple String value returned from the methods on this class. + *

+ * This class does not provide ways to access document-level content. Nor does + * it provide ways to access data relative to the Document level. Use absolute + * methods to access data from the Document level. + *

+ * The methods on this class assume that the Document is above the top-most + * Element in the XML tree. The top-most Element is the one that does not have + * a parent Element (although it may have a parent Document). As a result, you + * can use Element data that is not attached to a JDOM Document. + *

+ * Detatched Attributes, and detached non-Element content are not treated the + * same. If you try to get an Absolute path to a detached Attribute or + * non-Element Content you will get an IllegalArgumentException. On the other + * hand it is legal to get the relative XPath for a detached node to itself ( + * but to some other node will cause an IllegalArgumentException because the + * nodes do not share a common ancestor). + *

+ * Note: As this class has no knowledge of the document + * content, the generated XPath expression rely on the document structure. Hence + * any modification of the structure of the document may invalidate the + * generated XPaths. + *

+ * + * @author Laurent Bihanic + * @author Rolf Lear + */ +public final class XPathHelper { + + /** + * Private constructor. + */ + private XPathHelper () { + // make constructor inaccessible. + } + + /** + * Appends the specified path token to the provided buffer followed by the + * position specification of the target node in its siblings list (if + * needed). + * + * @param node + * the target node for the XPath expression. + * @param siblings + * the siblings of the target node. + * @param pathToken + * the path token identifying the target node. + * @param buffer + * the buffer to which appending the XPath sub-expression or + * null if the method shall allocate a new buffer. + * @return the XPath sub-expression to select the target node among its + * siblings. + */ + private static StringBuilder getPositionPath(Object node, List siblings, + String pathToken, StringBuilder buffer) { + + buffer.append(pathToken); + + if (siblings != null) { + int position = 0; + final Iterator i = siblings.iterator(); + while (i.hasNext()) { + position++; + if (i.next() == node) + break; + } + if (position > 1 || i.hasNext()) { + // the item is not at the first location, ot there are more + // locations. in other words, indexing is required. + buffer.append('[').append(position).append(']'); + } + } + return buffer; + } + + /** + * Calculate a single stage of an XPath query. + * + * @param nsa + * The token to get the relative-to-parent XPath for + * @param buffer + * The buffer to append the relative stage to + * @return The same buffer as was input. + */ + private static final StringBuilder getSingleStep(final NamespaceAware nsa, + final StringBuilder buffer) { + if (nsa instanceof Content) { + + final Content content = (Content) nsa; + + final Parent pnt = content.getParent(); + + if (content instanceof Text) { // OR CDATA! + + final List sibs = pnt == null ? null : + pnt.getContent(AbstractFilter.toFilter(Filters.text())); // CDATA + return getPositionPath(content, sibs, "text()", buffer); + + } else if (content instanceof Comment) { + + final List sibs = pnt == null ? null : + pnt.getContent(AbstractFilter.toFilter(Filters.comment())); + return getPositionPath(content, sibs, "comment()", buffer); + + } else if (content instanceof ProcessingInstruction) { + + final List sibs = pnt == null ? null : + pnt.getContent(AbstractFilter.toFilter(Filters.processinginstruction())); + return getPositionPath(content, sibs, + "processing-instruction()", buffer); + + } else if (content instanceof Element && + ((Element) content).getNamespace() == Namespace.NO_NAMESPACE) { + + // simple XPath to a no-namespace Element. + + final String ename = ((Element) content).getName(); + final List sibs = (pnt instanceof Element) ? ((Element)pnt) + .getChildren(ename) : null; + return getPositionPath(content, sibs, ename, buffer); + + } else if (content instanceof Element) { + + // complex XPath to an Element with Namespace... + // we do not want to have to prefix namespaces because that is + // essentially impossible to get right with the new JDOM2 API. + final Element emt = (Element)content; + + // Note, the getChildren compares only the URI (not the prefix) + // so the results are the same as an XPath would be. + final List sibs = (pnt instanceof Element) ? + ((Element)pnt).getChildren(emt.getName(), emt.getNamespace()) : null; + String xps = "*[local-name() = '" + emt.getName() + + "' and namespace-uri() = '" + + emt.getNamespaceURI() + "']"; + return getPositionPath(content, sibs, xps, buffer); + + } else { + final List sibs = pnt == null ? Collections + .singletonList(nsa) : pnt.getContent(); + return getPositionPath(content, sibs, "node()", buffer); + + } + } else if (nsa instanceof Attribute) { + Attribute att = (Attribute) nsa; + if (att.getNamespace() == Namespace.NO_NAMESPACE) { + buffer.append("@").append(att.getName()); + } else { + buffer.append("@*[local-name() = '").append(att.getName()); + buffer.append("' and namespace-uri() = '"); + buffer.append(att.getNamespaceURI()).append("']"); + } + } + + // do nothing... + return buffer; + } + + /** + * Returns the path to the specified toElement from the + * specified from Element. The from Element must have a common + * ancestor Element with the to Element. + *

+ * + * @param from + * the Element the generated path shall select relative to. + * @param to + * the Content the generated path shall select. + * @param sb + * the StringBuilder to append the path to. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if the from and to Elements have no common ancestor. + */ + private static StringBuilder getRelativeElementPath(final Element from, + final Parent to, final StringBuilder sb) { + if (from == to) { + sb.append("."); + return sb; + } + + // ToStack will be a chain of Elements from the to element to the + // root element, but will be 'short' if it encounters the from Element + // itself. + final ArrayList tostack = new ArrayList(); + Parent p = to; + while (p != null && p != from) { + tostack.add(p); + p = p.getParent(); + } + + // the number of steps we will have in the resulting path (potentially) + int pos = tostack.size(); + + if (p != from) { + // the from is not a direct ancestor of the to. + // we need to find where the common ancestor is between from and to + // we use the 'pos' variable to locate the common ancestor. + // pos is a pointer in to the tostack. + Parent f = from; + int fcnt = 0; + // note that we search for 'pos' here... it will be set. + while (f != null && (pos = locate(f, tostack)) < 0) { + // go up the from ELement's ancestry until we intersect with the + // to Element's Ancestry. + fcnt++; + f = f.getParent(); + } + if (f == null) { + throw new IllegalArgumentException( + "The 'from' and 'to' Element have no common ancestor."); + } + // OK, we have counted how far up the ancestry we need to go, so + // add the steps to the XPath. + while (--fcnt >= 0) { + sb.append("../"); + } + } + // we have the common point in the ancestry, indicated by 'pos'. + // we walk down the 'to' side of the tree until we get to the target. + while (--pos >= 0) { + getSingleStep(tostack.get(pos), sb); + sb.append("/"); + } + // we automatically append '/' in the loop, so we remove the last '/' + sb.setLength(sb.length() - 1); + return sb; + } + + /** + * Do an identity search in an array for a specific value. + * + * @param f + * The Element to search for. + * @param tostack + * The list to search in. + * @return the position of the f value in the tostack. + */ + private static int locate(final Parent f, final List tostack) { + // a somewhat naive search... ArrayList it is fast enough though. + int ret = tostack.size(); + while (--ret >= 0) { + if (f == tostack.get(ret)) { + return ret; + } + } + return -1; + } + + /** + * Returns the relative path from the given from Content to the specified to + * Content as an XPath expression. + * + * @param from + * the Content from which the the generated path shall be applied. + * @param to + * the Content the generated path shall select. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if to and from are not part of the same + * XML tree + */ + public static String getRelativePath(final Content from, final Content to) { + if (from == null) { + throw new NullPointerException( + "Cannot create a path from a null target"); + } + if (to == null) { + throw new NullPointerException( + "Cannot create a path to a null target"); + } + StringBuilder sb = new StringBuilder(); + if (from == to) { + return "."; + } + final Element efrom = (from instanceof Element) ? (Element) from : from + .getParentElement(); + if (from != efrom) { + sb.append("../"); + } + if (to instanceof Element) { + getRelativeElementPath(efrom, (Element) to, sb); + } else { + final Parent telement = to.getParent(); + if (telement == null) { + throw new IllegalArgumentException( + "Cannot get a relative XPath to detached content."); + } + getRelativeElementPath(efrom, telement, sb); + sb.append("/"); + getSingleStep(to, sb); + } + return sb.toString(); + } + + /** + * Returns the relative path from the given from Content to the specified to + * Attribute as an XPath expression. + * + * @param from + * the Content from which the the generated path shall be applied. + * @param to + * the Attribute the generated path shall select. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if to and from are not part of the same + * XML tree + */ + public static String getRelativePath(final Content from, final Attribute to) { + if (from == null) { + throw new NullPointerException( + "Cannot create a path from a null Content"); + } + if (to == null) { + throw new NullPointerException( + "Cannot create a path to a null Attribute"); + } + final Element t = to.getParent(); + if (t == null) { + throw new IllegalArgumentException( + "Cannot create a path to detached Attribute"); + } + StringBuilder sb = new StringBuilder(getRelativePath(from, t)); + sb.append("/"); + getSingleStep(to, sb); + return sb.toString(); + } + + /** + * Returns the relative path from the given from Attribute to the specified + * to Attribute as an XPath expression. + * + * @param from + * the Attribute from which the the generated path shall be applied. + * @param to + * the Attribute the generated path shall select. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if to and from are not part of the same + * XML tree + */ + public static String getRelativePath(final Attribute from, + final Attribute to) { + if (from == null) { + throw new NullPointerException( + "Cannot create a path from a null 'from'"); + } + if (to == null) { + throw new NullPointerException( + "Cannot create a path to a null target"); + } + if (from == to) { + return "."; + } + + final Element f = from.getParent(); + if (f == null) { + throw new IllegalArgumentException( + "Cannot create a path from a detached attrbibute"); + } + + return "../" + getRelativePath(f, to); + } + + /** + * Returns the relative path from the given from Attribute to the specified + * to Content as an XPath expression. + * + * @param from + * the Attribute from which the the generated path shall be applied. + * @param to + * the Content the generated path shall select. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if to and from are not part of the same + * XML tree + */ + public static String getRelativePath(final Attribute from, final Content to) { + if (from == null) { + throw new NullPointerException( + "Cannot create a path from a null 'from'"); + } + if (to == null) { + throw new NullPointerException( + "Cannot create a path to a null target"); + } + final Element f = from.getParent(); + if (f == null) { + throw new IllegalArgumentException( + "Cannot create a path from a detached attrbibute"); + } + if (f == to) { + return ".."; + } + return "../" + getRelativePath(f, to); + } + + /** + * Returns the absolute path to the specified to Content. + * + * @param to + * the Content the generated path shall select. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if to is not an Element and it is detached. + */ + public static String getAbsolutePath(final Content to) { + if (to == null) { + throw new NullPointerException( + "Cannot create a path to a null target"); + } + + final StringBuilder sb = new StringBuilder(); + + final Element t = (to instanceof Element) ? (Element) to : to + .getParentElement(); + if (t == null) { + if (to.getParent() == null) { + throw new IllegalArgumentException( + "Cannot create a path to detached target"); + } + // otherwise it must be at the document level. + sb.append("/"); + getSingleStep(to, sb); + return sb.toString(); + } + Element r = t; + while (r.getParentElement() != null) { + r = r.getParentElement(); + } + sb.append("/"); + getSingleStep(r, sb); + if (r != t) { + sb.append("/"); + getRelativeElementPath(r, t, sb); + } + if (t != to) { + sb.append("/"); + getSingleStep(to, sb); + } + return sb.toString(); + } + + /** + * Returns the absolute path to the specified to Content. + * + * @param to + * the Content the generated path shall select. + * @return an XPath expression to select the specified node. + * @throws IllegalArgumentException + * if to is detached. + */ + public static String getAbsolutePath(final Attribute to) { + if (to == null) { + throw new NullPointerException( + "Cannot create a path to a null target"); + } + + final Element t = to.getParent(); + if (t == null) { + throw new IllegalArgumentException( + "Cannot create a path to detached target"); + } + Element r = t; + while (r.getParentElement() != null) { + r = r.getParentElement(); + } + final StringBuilder sb = new StringBuilder(); + + sb.append("/"); + getSingleStep(r, sb); + if (t != r) { + sb.append("/"); + getRelativeElementPath(r, t, sb); + } + sb.append("/"); + getSingleStep(to, sb); + return sb.toString(); + } + +} diff --git a/core/src/java/org/jdom/xpath/jaxen/JDOM2Navigator.java b/core/src/java/org/jdom/xpath/jaxen/JDOM2Navigator.java new file mode 100644 index 0000000..9e08f81 --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/JDOM2Navigator.java @@ -0,0 +1,72 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +/** + * Simply extend the core Navigator to make it all final. + * + * @author Rolf Lear + * + */ +final class JDOM2Navigator extends JDOMCoreNavigator { + + // just make the class final. + + /** + * Standard JDOM2 Serialization. Default mechanism. + */ + private static final long serialVersionUID = 200L; + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/xpath/jaxen/JDOMCoreNavigator.java b/core/src/java/org/jdom/xpath/jaxen/JDOMCoreNavigator.java new file mode 100644 index 0000000..3f057dc --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/JDOMCoreNavigator.java @@ -0,0 +1,339 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +import java.io.IOException; +import java.util.Arrays; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; + +import org.jaxen.BaseXPath; +import org.jaxen.DefaultNavigator; +import org.jaxen.FunctionCallException; +import org.jaxen.JaxenConstants; +import org.jaxen.UnsupportedAxisException; +import org.jaxen.XPath; +import org.jaxen.saxpath.SAXPathException; +import org.jaxen.util.SingleObjectIterator; + +import org.jdom.Attribute; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.input.SAXBuilder; + +class JDOMCoreNavigator extends DefaultNavigator { + + /** + * Standard JDOM2 Serialization. Default mechanism. + */ + private static final long serialVersionUID = 200L; + + private transient IdentityHashMap emtnsmap + = new IdentityHashMap(); + + void reset() { + emtnsmap.clear(); + } + + + @Override + public final XPath parseXPath(String path) throws SAXPathException { + return new BaseXPath(path, this); + } + + @Override + public final Object getDocument(String url) throws FunctionCallException { + SAXBuilder sb = new SAXBuilder(); + try { + return sb.build(url); + } catch (JDOMException e) { + throw new FunctionCallException("Failed to parse " + url, e); + } catch (IOException e) { + throw new FunctionCallException("Failed to access " + url, e); + } + } + + @Override + public final boolean isText(Object isit) { + return isit instanceof Text; + } + + @Override + public final boolean isProcessingInstruction(Object isit) { + return isit instanceof ProcessingInstruction; + } + + @Override + public final boolean isNamespace(Object isit) { + return (isit instanceof NamespaceContainer); + } + + @Override + public final boolean isElement(Object isit) { + return isit instanceof Element; + } + + @Override + public final boolean isDocument(Object isit) { + return isit instanceof Document; + } + + @Override + public final boolean isComment(Object isit) { + return isit instanceof Comment; + } + + @Override + public final boolean isAttribute(Object isit) { + return isit instanceof Attribute; + } + + @Override + public final String getTextStringValue(Object text) { + // CDATA is a subclass of Text + return ((Text)text).getText(); + } + + @Override + public final String getNamespaceStringValue(Object namespace) { + return ((NamespaceContainer)namespace).getNamespace().getURI(); + } + + @Override + public final String getNamespacePrefix(Object namespace) { + return ((NamespaceContainer)namespace).getNamespace().getPrefix(); + } + + private final void recurseElementText(Element element, StringBuilder sb) { + for (Iterator it = element.getContent().iterator(); it.hasNext(); ) { + Content c = (Content)it.next(); + if (c instanceof Element) { + recurseElementText((Element)c, sb); + } else if (c instanceof Text) { + sb.append(((Text)c).getText()); + } + } + } + + @Override + public final String getElementStringValue(Object element) { + StringBuilder sb = new StringBuilder(); + recurseElementText((Element)element, sb); + return sb.toString(); + } + + @Override + public final String getElementQName(Object element) { + Element e = (Element)element; + if (e.getNamespace().getPrefix().length() == 0) { + return e.getName(); + } + return e.getNamespacePrefix() + ":" + e.getName(); + } + + @Override + public final String getElementNamespaceUri(Object element) { + return ((Element)element).getNamespaceURI(); + } + + @Override + public final String getElementName(Object element) { + return ((Element)element).getName(); + } + + @Override + public final String getCommentStringValue(Object comment) { + return ((Comment)comment).getValue(); + } + + @Override + public final String getAttributeStringValue(Object attribute) { + return ((Attribute)attribute).getValue(); + } + + @Override + public final String getAttributeQName(Object att) { + Attribute attribute = (Attribute)att; + if (attribute.getNamespacePrefix().length() == 0) { + return attribute.getName(); + } + return attribute.getNamespacePrefix() + ":" + attribute.getName(); + } + + @Override + public final String getAttributeNamespaceUri(Object attribute) { + return ((Attribute)attribute).getNamespaceURI(); + } + + @Override + public final String getAttributeName(Object attribute) { + return ((Attribute)attribute).getName(); + } + + @Override + public final String getProcessingInstructionTarget(Object pi) { + return ((ProcessingInstruction)pi).getTarget(); + } + + @Override + public final String getProcessingInstructionData(Object pi) { + return ((ProcessingInstruction)pi).getData(); + } + + @Override + public final Object getDocumentNode(Object contextNode) { + if (contextNode instanceof Document) { + return contextNode; + } + if (contextNode instanceof NamespaceContainer) { + return ((NamespaceContainer)contextNode).getParentElement().getDocument(); + } + if (contextNode instanceof Attribute) { + return ((Attribute)contextNode).getDocument(); + } + return ((Content)contextNode).getDocument(); + } + + @Override + public final Object getParentNode(Object contextNode) throws UnsupportedAxisException { + if (contextNode instanceof Document) { + return null; + } + if (contextNode instanceof NamespaceContainer) { + return ((NamespaceContainer)contextNode).getParentElement(); + } + if (contextNode instanceof Content) { + return ((Content)contextNode).getParent(); + } + if (contextNode instanceof Attribute) { + return ((Attribute)contextNode).getParent(); + } + return null; + } + + @Override + public final Iterator getAttributeAxisIterator(Object contextNode) throws UnsupportedAxisException { + if (isElement(contextNode) && ((Element)contextNode).hasAttributes()) { + return ((Element)contextNode).getAttributes().iterator(); + } + return JaxenConstants.EMPTY_ITERATOR; + } + + @Override + public final Iterator getChildAxisIterator(Object contextNode) throws UnsupportedAxisException { + if (contextNode instanceof Parent) { + return ((Parent)contextNode).getContent().iterator(); + } + return JaxenConstants.EMPTY_ITERATOR; + } + + @Override + public final Iterator getNamespaceAxisIterator(final Object contextNode) throws UnsupportedAxisException { + //The namespace axis applies to Elements only in XPath. + if ( !isElement(contextNode) ) { + return JaxenConstants.EMPTY_ITERATOR; + } + NamespaceContainer[] ret = emtnsmap.get(contextNode); + if (ret == null) { + List nsl = ((Element)contextNode).getNamespacesInScope(); + ret = new NamespaceContainer[nsl.size()]; + int i = 0; + for (Namespace ns : nsl) { + ret[i++] = new NamespaceContainer(ns, (Element)contextNode); + } + emtnsmap.put((Element)contextNode, ret); + } + + return Arrays.asList(ret).iterator(); + + } + + @Override + public final Iterator getParentAxisIterator(Object contextNode) throws UnsupportedAxisException { + + Parent p = null; + if (contextNode instanceof Content) { + p = ((Content)contextNode).getParent(); + } else if (contextNode instanceof NamespaceContainer) { + p = ((NamespaceContainer)contextNode).getParentElement(); + } else if (contextNode instanceof Attribute) { + p = ((Attribute)contextNode).getParent(); + } + if (p != null) { + return new SingleObjectIterator(p); + } + return JaxenConstants.EMPTY_ITERATOR; + } + + private void readObject(java.io.ObjectInputStream in) + throws IOException, ClassNotFoundException { + in.defaultReadObject(); + emtnsmap = new IdentityHashMap(); + } + + private void writeObject(java.io.ObjectOutputStream out) + throws IOException { + out.defaultWriteObject(); + } + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/xpath/jaxen/JDOMNavigator.java b/core/src/java/org/jdom/xpath/jaxen/JDOMNavigator.java new file mode 100644 index 0000000..8585799 --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/JDOMNavigator.java @@ -0,0 +1,113 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +import java.util.HashMap; +import java.util.List; + +import org.jaxen.NamespaceContext; + +import org.jdom.Namespace; +import org.jdom.NamespaceAware; + +final class JDOMNavigator extends JDOMCoreNavigator implements NamespaceContext { + + /** + * Standard JDOM2 Serialization. Default mechanism. + */ + private static final long serialVersionUID = 200L; + + private final HashMap nsFromContext = new HashMap(); + private final HashMap nsFromUser = new HashMap(); + + @Override + void reset() { + super.reset(); + nsFromContext.clear(); + } + + void setContext(Object node) { + nsFromContext.clear(); + + List nsl = null; + if (node instanceof NamespaceAware) { + nsl = ((NamespaceAware)node).getNamespacesInScope(); + } else if (node instanceof NamespaceContainer) { + nsl = ((NamespaceContainer)node).getParentElement().getNamespacesInScope(); + } + if (nsl != null) { + for (Namespace ns : nsl) { + nsFromContext.put(ns.getPrefix(), ns.getURI()); + } + } + } + + void includeNamespace(Namespace namespace) { + nsFromUser.put(namespace.getPrefix(), namespace.getURI()); + } + + @Override + public String translateNamespacePrefixToUri(String prefix) { + if (prefix == null) { + return null; + } + String uri = nsFromUser.get(prefix); + if (uri != null) { + return uri; + } + return nsFromContext.get(prefix); + } + +} \ No newline at end of file diff --git a/core/src/java/org/jdom/xpath/jaxen/JDOMXPath.java b/core/src/java/org/jdom/xpath/jaxen/JDOMXPath.java new file mode 100644 index 0000000..e682d86 --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/JDOMXPath.java @@ -0,0 +1,351 @@ +/*-- + + Copyright (C) 2000-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.jaxen.BaseXPath; +import org.jaxen.JaxenException; +import org.jaxen.SimpleVariableContext; +import org.jaxen.XPath; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; + +/** + * A concrete XPath implementation for Jaxen. This class must be public because + * the main JDOM XPath class needs to access the class, and the constructor. + * + * The generic type of the returned values from this XPath instance. + * @author Laurent Bihanic + * @deprecated replaced by compiled version. + */ +@Deprecated +public class JDOMXPath extends org.jdom.xpath.XPath { + + /** + * Default mechanism. + * The serialization for this class is broken. It is only included here for + * compatibility with JDOM 1.x + */ + private static final long serialVersionUID = 200L; + + + /** + * The compiled XPath object to select nodes. This attribute cannot be made + * final as it needs to be set upon object deserialization. + */ + private transient XPath xPath; + + /** + * The current context for XPath expression evaluation. The navigator is + * responsible for exposing JDOM content to Jaxen, including the wrapping of + * Namespace instances in NamespaceContainer + *

+ * Because of the need to wrap Namespace, we also need to unwrap namespace. + * Further, we can't re-use the details from one 'selectNodes' to another + * because the Document tree may have been modified between, and also, we do + * not want to be holding on to memory. + *

+ * Finally, we want to pre-load the NamespaceContext with the namespaces + * that are in scope for the contextNode being searched. + *

+ * So, we need to reset the Navigator before and after each use. try{} + * finally {} to the rescue. + */ + private final JDOMNavigator navigator = new JDOMNavigator(); + + /** + * Same story, need to be able to strip NamespaceContainer instances from + * Namespace content. + * + * @param o + * A result object which could potentially be a NamespaceContainer + * @return The input parameter unless it is a NamespaceContainer in which + * case return the wrapped Namespace + */ + private static final Object unWrapNS(Object o) { + if (o instanceof NamespaceContainer) { + return ((NamespaceContainer) o).getNamespace(); + } + return o; + } + + /** + * Same story, need to be able to replace NamespaceContainer instances with + * Namespace content. + * + * @param results + * A list potentially containing NamespaceContainer instances + * @return The parameter list with NamespaceContainer instances replaced by + * the wrapped Namespace instances. + */ + private static final List unWrap(List results) { + final ArrayList ret = new ArrayList(results.size()); + for (Iterator it = results.iterator(); it.hasNext();) { + ret.add(unWrapNS(it.next())); + } + return ret; + } + + /** + * Creates a new XPath wrapper object, compiling the specified XPath + * expression. + * + * @param expr + * the XPath expression to wrap. + * @throws JDOMException + * if the XPath expression is invalid. + */ + public JDOMXPath(String expr) + throws JDOMException { + setXPath(expr); + } + + /** + * Evaluates the wrapped XPath expression and returns the list of selected + * items. + * + * @param context + * the node to use as context for evaluating the XPath expression. + * @return the list of selected items, which may be of types: {@link Element} + * , {@link Attribute}, {@link Text}, {@link CDATA}, {@link Comment} + * , {@link ProcessingInstruction}, Boolean, Double, or String. + * @throws JDOMException + * if the evaluation of the XPath expression on the specified + * context failed. + */ + @Override + public List selectNodes(Object context) + throws JDOMException { + try { + navigator.setContext(context); + + return unWrap(xPath.selectNodes(context)); + } catch (JaxenException ex1) { + throw new JDOMException( + "XPath error while evaluating \"" + xPath.toString() + + "\": " + ex1.getMessage(), ex1); + } finally { + navigator.reset(); + } + } + + /** + * Evaluates the wrapped XPath expression and returns the first entry in the + * list of selected nodes (or atomics). + * + * @param context + * the node to use as context for evaluating the XPath expression. + * @return the first selected item, which may be of types: {@link Element}, + * {@link Attribute}, {@link Text}, {@link CDATA}, {@link Comment}, + * {@link ProcessingInstruction}, Boolean, Double, String, or + * null if no item was selected. + * @throws JDOMException + * if the evaluation of the XPath expression on the specified + * context failed. + */ + @Override + public Object selectSingleNode(Object context) + throws JDOMException { + try { + navigator.setContext(context); + + return unWrapNS(xPath.selectSingleNode(context)); + } catch (JaxenException ex1) { + throw new JDOMException( + "XPath error while evaluating \"" + xPath.toString() + + "\": " + ex1.getMessage(), ex1); + } finally { + navigator.reset(); + } + } + + /** + * Returns the string value of the first node selected by applying the + * wrapped XPath expression to the given context. + * + * @param context + * the element to use as context for evaluating the XPath expression. + * @return the string value of the first node selected by applying the + * wrapped XPath expression to the given context. + * @throws JDOMException + * if the XPath expression is invalid or its evaluation on the + * specified context failed. + */ + @Override + public String valueOf(Object context) throws JDOMException { + try { + navigator.setContext(context); + + return xPath.stringValueOf(context); + } catch (JaxenException ex1) { + throw new JDOMException( + "XPath error while evaluating \"" + xPath.toString() + + "\": " + ex1.getMessage(), ex1); + } finally { + navigator.reset(); + } + } + + /** + * Returns the number value of the first item selected by applying the + * wrapped XPath expression to the given context. + * + * @param context + * the element to use as context for evaluating the XPath expression. + * @return the number value of the first item selected by applying the + * wrapped XPath expression to the given context, null + * if no node was selected or the special value + * {@link java.lang.Double#NaN} (Not-a-Number) if the selected value + * can not be converted into a number value. + * @throws JDOMException + * if the XPath expression is invalid or its evaluation on the + * specified context failed. + */ + @Override + public Number numberValueOf(Object context) throws JDOMException { + try { + navigator.setContext(context); + + return xPath.numberValueOf(context); + } catch (JaxenException ex1) { + throw new JDOMException( + "XPath error while evaluating \"" + xPath.toString() + + "\": " + ex1.getMessage(), ex1); + } finally { + navigator.reset(); + } + } + + /** + * Defines an XPath variable and sets its value. + * + * @param name + * the variable name. + * @param value + * the variable value. + * @throws IllegalArgumentException + * if name is not a valid XPath variable name or if the + * value type is not supported by the underlying implementation + */ + @Override + public void setVariable(String name, Object value) + throws IllegalArgumentException { + Object o = xPath.getVariableContext(); + if (o instanceof SimpleVariableContext) { + ((SimpleVariableContext) o).setVariableValue(null, name, value); + } + } + + /** + * Adds a namespace definition to the list of namespaces known of this XPath + * expression. + *

+ * Note: In XPath, there is no such thing as a 'default + * namespace'. The empty prefix always resolves to the empty + * namespace URI. + *

+ * + * @param namespace + * the namespace. + */ + @Override + public void addNamespace(Namespace namespace) { + navigator.includeNamespace(namespace); + } + + /** + * Returns the wrapped XPath expression as a string. + * + * @return the wrapped XPath expression as a string. + */ + @Override + public String getXPath() { + return (xPath.toString()); + } + + /** + * Compiles and sets the XPath expression wrapped by this object. + * + * @param expr + * the XPath expression to wrap. + * @throws JDOMException + * if the XPath expression is invalid. + */ + private void setXPath(String expr) throws JDOMException { + try { + xPath = new BaseXPath(expr, navigator); + xPath.setNamespaceContext(navigator); + } catch (Exception ex1) { + throw new JDOMException("Invalid XPath expression: \"" + + expr + "\"", ex1); + } + } + + @Override + public String toString() { + return (String.format("[XPath: %s]", xPath.toString())); + } + +} diff --git a/core/src/java/org/jdom/xpath/jaxen/JaxenCompiled.java b/core/src/java/org/jdom/xpath/jaxen/JaxenCompiled.java new file mode 100644 index 0000000..7605b79 --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/JaxenCompiled.java @@ -0,0 +1,224 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.jaxen.BaseXPath; +import org.jaxen.JaxenException; +import org.jaxen.NamespaceContext; +import org.jaxen.UnresolvableException; +import org.jaxen.VariableContext; +import org.jaxen.XPath; + +import org.jdom.Namespace; +import org.jdom.filter2.Filter; +import org.jdom.xpath.util.AbstractXPathCompiled; + +/** + * Jaxen specific code for XPath management. + * + * @author Rolf Lear + * @param + * The generic type of returned data. + */ +class JaxenCompiled extends AbstractXPathCompiled implements + NamespaceContext, VariableContext { + + /** + * Same story, need to be able to strip NamespaceContainer instances from + * Namespace content. + * + * @param o + * A result object which could potentially be a NamespaceContainer + * @return The input parameter unless it is a NamespaceContainer in which + * case return the wrapped Namespace + */ + private static final Object unWrapNS(Object o) { + if (o instanceof NamespaceContainer) { + return ((NamespaceContainer) o).getNamespace(); + } + return o; + } + + /** + * Same story, need to be able to replace NamespaceContainer instances with + * Namespace content. + * + * @param results + * A list potentially containing NamespaceContainer instances + * @return The parameter list with NamespaceContainer instances replaced by + * the wrapped Namespace instances. + */ + private static final List unWrap(List results) { + final ArrayList ret = new ArrayList(results.size()); + for (Iterator it = results.iterator(); it.hasNext();) { + ret.add(unWrapNS(it.next())); + } + return ret; + } + + /** + * The compiled XPath object to select nodes. This attribute can not be made + * final as it needs to be set upon object deserialization. + */ + private final XPath xPath; + + /** + * The current context for XPath expression evaluation. The navigator is + * responsible for exposing JDOM content to Jaxen, including the wrapping of + * Namespace instances in NamespaceContainer + *

+ * Because of the need to wrap Namespace, we also need to unwrap namespace. + * Further, we can't re-use the details from one 'selectNodes' to another + * because the Document tree may have been modfied between, and also, we do + * not want to be holding on to memory. + *

+ * Finally, we want to pre-load the NamespaceContext with the namespaces + * that are in scope for the contextNode being searched. + *

+ * So, we need to reset the Navigator before and after each use. try{} + * finally {} to the rescue. + */ + private final JDOM2Navigator navigator = new JDOM2Navigator(); + + /** + * @param expression The XPath expression + * @param filter The coercion filter + * @param variables The XPath variable context + * @param namespaces The XPath namespace context + */ + public JaxenCompiled(String expression, Filter filter, + Map variables, Namespace[] namespaces) { + super(expression, filter, variables, namespaces); + try { + xPath = new BaseXPath(expression, navigator); + } catch (JaxenException e) { + throw new IllegalArgumentException("Unable to compile '" + expression + + "'. See Cause.", e); + } + xPath.setNamespaceContext(this); + xPath.setVariableContext(this); + } + + /** + * Make a copy-constructor available to the clone() method. + * This is simpler than trying to do a deep clone anyway. + * + * @param toclone The JaxenCompiled instance to clone + */ + private JaxenCompiled(JaxenCompiled toclone) { + this(toclone.getExpression(), toclone.getFilter(), toclone.getVariables(), toclone.getNamespaces()); + } + + @Override + public String translateNamespacePrefixToUri(String prefix) { + return getNamespace(prefix).getURI(); + } + + @Override + public Object getVariableValue(String namespaceURI, String prefix, + String localName) throws UnresolvableException { + if (namespaceURI == null) { + namespaceURI = ""; + } + if (prefix == null) { + prefix = ""; + } + try { + if ("".equals(namespaceURI)) { + namespaceURI = getNamespace(prefix).getURI(); + } + return getVariable(localName, Namespace.getNamespace(namespaceURI)); + } catch (IllegalArgumentException e) { + throw new UnresolvableException("Unable to resolve variable " + + localName + " in namespace '" + namespaceURI + + "' to a vaulue."); + } + } + + @Override + protected List evaluateRawAll(Object context) { + try { + return unWrap(xPath.selectNodes(context)); + } catch (JaxenException e) { + throw new IllegalStateException( + "Unable to evaluate expression. See cause", e); + } + } + + @Override + protected Object evaluateRawFirst(Object context) { + try { + return unWrapNS(xPath.selectSingleNode(context)); + } catch (JaxenException e) { + throw new IllegalStateException( + "Unable to evaluate expression. See cause", e); + } + } + + @Override + public JaxenCompiled clone() { + // Use a copy-constructor instead of a deep clone. + // we have a couple of final variables on this class that we cannot share + // between instances, and the Jaxen xpath variable is pretty complicated to reconstruct + // anyway. Easier to just reconstruct it. + return new JaxenCompiled(this); + } + +} diff --git a/core/src/java/org/jdom/xpath/jaxen/JaxenXPathFactory.java b/core/src/java/org/jdom/xpath/jaxen/JaxenXPathFactory.java new file mode 100644 index 0000000..5710629 --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/JaxenXPathFactory.java @@ -0,0 +1,85 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +import java.util.Map; + +import org.jdom.Namespace; +import org.jdom.filter2.Filter; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; + +/** + * This simple Factory creates XPath instances tailored to the Jaxen library. + * + * @author Rolf Lear + * + */ +public class JaxenXPathFactory extends XPathFactory { + + /** + * The public default constructor used by the XPathFactory. + */ + public JaxenXPathFactory() { + // do nothing. + } + + @Override + public XPathExpression compile(String expression, Filter filter, + Map variables, Namespace... namespaces) { + return new JaxenCompiled(expression, filter, variables, namespaces); + } + +} diff --git a/core/src/java/org/jdom/xpath/jaxen/NamespaceContainer.java b/core/src/java/org/jdom/xpath/jaxen/NamespaceContainer.java new file mode 100644 index 0000000..b7edf04 --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/NamespaceContainer.java @@ -0,0 +1,98 @@ +/*-- + + Copyright (C) 2011-2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.jaxen; + +import org.jdom.Element; +import org.jdom.Namespace; + +/** + * XPath requires that the namespace nodes are linked to an Element. + * JDOM does not have such a relationship, so we improvise. + * Jaxen uses the identity equals ( == ) when comparing + * one 'node' to another, so we have to make sure that within the context of + * Jaxen we always return the same instances of NamespaceContainer each time. + * Further, for jaxen the instance of a Namespace for one Node cannot be the + * same instance of the same Namespace on a different Node... + *

+ * This all also means that all Jaxen interaction is done with NamespaceContainer + * and not the JDOM Namespace, which in turn means that when Jaxen returns + * a NamespaceContainer node, it has to be unwrapped to a simple Namespace. + * + * @author rolf + * + */ +final class NamespaceContainer { + + private final Namespace ns; + private final Element emt; + + public NamespaceContainer(Namespace ns, Element emt) { + this.ns = ns; + this.emt = emt; + } + + public Namespace getNamespace() { + return ns; + } + + public Element getParentElement() { + return emt; + } + + @Override + public String toString() { + return ns.getPrefix() + "=" + ns.getURI(); + } +} \ No newline at end of file diff --git a/core/src/java/org/jdom/xpath/jaxen/package.html b/core/src/java/org/jdom/xpath/jaxen/package.html new file mode 100644 index 0000000..1b2489c --- /dev/null +++ b/core/src/java/org/jdom/xpath/jaxen/package.html @@ -0,0 +1,3 @@ + + Support for the Jaxen XPath Library. + diff --git a/core/src/java/org/jdom/xpath/package.html b/core/src/java/org/jdom/xpath/package.html new file mode 100644 index 0000000..4a3bbe6 --- /dev/null +++ b/core/src/java/org/jdom/xpath/package.html @@ -0,0 +1,15 @@ + + + Support for XPath from within JDOM. XPath provides a common interface + with a pluggable back-end. The default back end is Jaxen. +

The JDOM 1.x API uses the XPath class as the entire API + interface. This has been deprecated, and replaced with XPathFactory, + XPathCompiled, and XPathBuilder. +

+ Please see the web page for the details on the + + JDOM2 XPath API change. +

+ The XPathHelper class provides static methods to create XPath queries + that identify specific JDOM nodes. + diff --git a/core/src/java/org/jdom/xpath/util/AbstractXPathCompiled.java b/core/src/java/org/jdom/xpath/util/AbstractXPathCompiled.java new file mode 100644 index 0000000..c92ed17 --- /dev/null +++ b/core/src/java/org/jdom/xpath/util/AbstractXPathCompiled.java @@ -0,0 +1,416 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.util; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jdom.Namespace; +import org.jdom.Verifier; +import org.jdom.filter2.Filter; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathDiagnostic; + +/** + * A mostly-implemented XPathExpression that only needs two methods to be + * implemented in order to satisfy the complete API. Subclasses of this + * MUST correctly override the clone() method which in turn + * should call super.clone(); + * + * @param + * The generic type of the returned values. + * @author Rolf Lear + */ +public abstract class AbstractXPathCompiled implements XPathExpression { + + private static final class NamespaceComparator implements Comparator { + @Override + public int compare(Namespace ns1, Namespace ns2) { + return ns1.getPrefix().compareTo(ns2.getPrefix()); + } + } + + private static final NamespaceComparator NSSORT = new NamespaceComparator(); + + /** + * Utility method to find a Namespace that has a given URI, and return the prefix. + * @param uri the URI to search for + * @param nsa the array of namespaces to search through + * @return the prefix of the namespace + */ + private static final String getPrefixForURI(final String uri, final Namespace[] nsa) { + for (final Namespace ns : nsa) { + if (ns.getURI().equals(uri)) { + return ns.getPrefix(); + } + } + throw new IllegalStateException("No namespace defined with URI " + uri); + } + + private final Map xnamespaces = new HashMap(); + // Not final to support cloning. + private Map> xvariables = new HashMap>(); + private final String xquery; + private final Filter xfilter; + + /** + * Construct an XPathExpression. + * + * @see XPathExpression for conditions which throw + * {@link NullPointerException} or {@link IllegalArgumentException}. + * @param query + * The XPath query + * @param filter + * The coercion filter. + * @param variables + * A map of variables. + * @param namespaces + * The namespaces referenced from the query. + */ + public AbstractXPathCompiled(final String query, final Filter filter, + final Map variables, final Namespace[] namespaces) { + if (query == null) { + throw new NullPointerException("Null query"); + } + if (filter == null) { + throw new NullPointerException("Null filter"); + } + xnamespaces.put(Namespace.NO_NAMESPACE.getPrefix(), + Namespace.NO_NAMESPACE); + if (namespaces != null) { + for (Namespace ns : namespaces) { + if (ns == null) { + throw new NullPointerException("Null namespace"); + } + final Namespace oldns = xnamespaces.put(ns.getPrefix(), ns); + if (oldns != null && oldns != ns) { + if (oldns == Namespace.NO_NAMESPACE) { + throw new IllegalArgumentException( + "The default (no prefix) Namespace URI for XPath queries is always" + + " '' and it cannot be redefined to '" + ns.getURI() + "'."); + } + throw new IllegalArgumentException( + "A Namespace with the prefix '" + ns.getPrefix() + + "' has already been declared."); + } + } + } + + if (variables != null) { + for (Map.Entry me : variables.entrySet()) { + final String qname = me.getKey(); + if (qname == null) { + throw new NullPointerException("Variable with a null name"); + } + final int p = qname.indexOf(':'); + final String pfx = p < 0 ? "" : qname.substring(0, p); + final String lname = p < 0 ? qname : qname.substring(p + 1); + + final String vpfxmsg = Verifier.checkNamespacePrefix(pfx); + if (vpfxmsg != null) { + throw new IllegalArgumentException("Prefix '" + pfx + + "' for variable " + qname + " is illegal: " + + vpfxmsg); + } + final String vnamemsg = Verifier.checkXMLName(lname); + if (vnamemsg != null) { + throw new IllegalArgumentException("Variable name '" + + lname + "' for variable " + qname + + " is illegal: " + vnamemsg); + } + + final Namespace ns = xnamespaces.get(pfx); + if (ns == null) { + throw new IllegalArgumentException("Prefix '" + pfx + + "' for variable " + qname + + " has not been assigned a Namespace."); + } + + Map vmap = xvariables.get(ns.getURI()); + if (vmap == null) { + vmap = new HashMap(); + xvariables.put(ns.getURI(), vmap); + } + + if (vmap.put(lname, me.getValue()) != null) { + throw new IllegalArgumentException("Variable with name " + + me.getKey() + "' has already been defined."); + } + } + } + xquery = query; + xfilter = filter; + } + + /** + * Subclasses of this AbstractXPathCompile class must call super.clone() in + * their clone methods! + *

+ * This would be a sample clone method from a subclass: + * + * + *

+	 * 		public XPathExpression<T> clone() {
+	 * 			{@literal @}SuppressWarnings("unchecked")
+	 * 			final MyXPathCompiled<T> ret = (MyXPathCompiled<T>)super.clone();
+	 * 			// change any fields that need to be cloned.
+	 * 			....
+	 * 			return ret;
+	 * 		}
+	 * 
+ * + * Here's the documentation from {@link XPathExpression#clone()} + *

+ * {@inheritDoc} + */ + @Override + public XPathExpression clone() { + AbstractXPathCompiled ret = null; + try { + @SuppressWarnings("unchecked") + final AbstractXPathCompiled c = (AbstractXPathCompiled) super + .clone(); + ret = c; + } catch (CloneNotSupportedException cnse) { + throw new IllegalStateException( + "Should never be getting a CloneNotSupportedException!", + cnse); + } + Map> vmt = new HashMap>(); + for (Map.Entry> me : xvariables.entrySet()) { + final Map cmap = new HashMap(); + for (Map.Entry ne : me.getValue().entrySet()) { + cmap.put(ne.getKey(), ne.getValue()); + } + vmt.put(me.getKey(), cmap); + } + ret.xvariables = vmt; + return ret; + } + + @Override + public final String getExpression() { + return xquery; + } + + @Override + public final Namespace getNamespace(final String prefix) { + final Namespace ns = xnamespaces.get(prefix); + if (ns == null) { + throw new IllegalArgumentException("Namespace with prefix '" + + prefix + "' has not been declared."); + } + return ns; + } + + @Override + public Namespace[] getNamespaces() { + final Namespace[] nsa = xnamespaces.values().toArray( + new Namespace[xnamespaces.size()]); + Arrays.sort(nsa, NSSORT); + return nsa; + } + + @Override + public final Object getVariable(final String name, Namespace uri) { + final Map vmap = + xvariables.get(uri == null ? "" : uri.getURI()); + if (vmap == null) { + throw new IllegalArgumentException("Variable with name '" + name + + "' in namespace '" + uri.getURI() + "' has not been declared."); + } + final Object ret = vmap.get(name); + if (ret == null) { + if (!vmap.containsKey(name)) { + throw new IllegalArgumentException("Variable with name '" + + name + "' in namespace '" + uri.getURI() + + "' has not been declared."); + } + // leave translating null variable values to the implementation. + return null; + } + return ret; + } + + @Override + public Object getVariable(String qname) { + if (qname == null) { + throw new NullPointerException( + "Cannot get variable value for null qname"); + } + final int pos = qname.indexOf(':'); + if (pos >= 0) { + return getVariable(qname.substring(pos + 1), + getNamespace(qname.substring(0, pos))); + } + return getVariable(qname, Namespace.NO_NAMESPACE); + } + + @Override + public Object setVariable(String name, Namespace uri, Object value) { + final Object ret = getVariable(name, uri); + // if that succeeded then we have it easy.... + xvariables.get(uri.getURI()).put(name, value); + return ret; + } + + @Override + public Object setVariable(String qname, Object value) { + if (qname == null) { + throw new NullPointerException( + "Cannot get variable value for null qname"); + } + final int pos = qname.indexOf(':'); + if (pos >= 0) { + return setVariable(qname.substring(pos + 1), + getNamespace(qname.substring(0, pos)), value); + } + return setVariable(qname, Namespace.NO_NAMESPACE, value); + } + + /** + * utility method that allows descendant classes to access the variables + * that were set on this expression, in a format that can be used in a constructor (qname/value). + * @return the variables set on this instance. + */ + protected Map getVariables() { + HashMap vars = new HashMap(); + Namespace[] nsa = getNamespaces(); + for (Map.Entry> ue : xvariables.entrySet()) { + final String uri = ue.getKey(); + final String pfx = getPrefixForURI(uri, nsa); + for (Map.Entry ve : ue.getValue().entrySet()) { + if ("".equals(pfx)) { + vars.put(ve.getKey(), ve.getValue()); + } else { + vars.put(pfx + ":" + ve.getKey(), ve.getValue()); + } + } + } + return vars; + } + + @Override + public final Filter getFilter() { + return xfilter; + } + + @Override + public List evaluate(Object context) { + return xfilter.filter(evaluateRawAll(context)); + } + + /** + * + */ + @Override + public T evaluateFirst(Object context) { + Object raw = evaluateRawFirst(context); + if (raw == null) { + return null; + } + return xfilter.filter(raw); + } + + @Override + public XPathDiagnostic diagnose(Object context, boolean firstonly) { + final List result = firstonly ? Collections + .singletonList(evaluateRawFirst(context)) + : evaluateRawAll(context); + return new XPathDiagnosticImpl(context, this, result, firstonly); + } + + @Override + public String toString() { + int nscnt = xnamespaces.size(); + int vcnt = 0; + for (Map cmap : xvariables.values()) { + vcnt += cmap.size(); + } + return String.format( + "[XPathExpression: %d namespaces and %d variables for query %s]", + nscnt, vcnt, getExpression()); + } + + /** + * This is the raw expression evaluator to be implemented by the back-end + * XPath library. + * + * @param context + * The context against which to evaluate the query + * @return A list of XPath results. + */ + protected abstract List evaluateRawAll(Object context); + + /** + * This is the raw expression evaluator to be implemented by the back-end + * XPath library. When this method is processed the implementing library is + * free to stop processing when the result that would be the first result is + * retrieved. + *

+ * Only the first value in the result will be processed (if any). + * + * @param context + * The context against which to evaluate the query + * @return The first item in the XPath results, or null if there are no + * results. + */ + protected abstract Object evaluateRawFirst(Object context); + +} diff --git a/core/src/java/org/jdom/xpath/util/XPathDiagnosticImpl.java b/core/src/java/org/jdom/xpath/util/XPathDiagnosticImpl.java new file mode 100644 index 0000000..0ba30cf --- /dev/null +++ b/core/src/java/org/jdom/xpath/util/XPathDiagnosticImpl.java @@ -0,0 +1,164 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.xpath.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jdom.filter2.Filter; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathDiagnostic; + +/** + * A diagnostic implementation useful for diagnosing problems in XPath results. + *

+ * This class tries to make all the data available as part of the internal + * structure which may assist people who are stepping-through the code from + * a debugging environment. + * + * @author Rolf Lear + * + * @param The generic type of the results from the {@link XPathExpression} + */ +public class XPathDiagnosticImpl implements XPathDiagnostic { + + /* + * Keep nice list references here to help users who debug and step through + * code. They can inspect the various lists directly. + */ + private final Object dcontext; + private final XPathExpression dxpath; + private final List draw; + private final List dfiltered; + private final List dresult; + private final boolean dfirstonly; + + /** + * Create a useful Diagnostic instance for tracing XPath query results. + * @param dcontext The context against which the XPath query was run. + * @param dxpath The {@link XPathExpression} instance which created this diagnostic. + * @param inraw The data as returned from the XPath library. + * @param dfirstonly If the XPath library was allowed to terminate after the first result. + */ + public XPathDiagnosticImpl(Object dcontext, XPathExpression dxpath, + List inraw, boolean dfirstonly) { + + final int sz = inraw.size(); + final List raw = new ArrayList(sz); + final List filtered = new ArrayList(sz); + final List result = new ArrayList(sz); + final Filter filter = dxpath.getFilter(); + + for (Object o : inraw) { + raw.add(o); + T t = filter.filter(o); + if (t == null) { + filtered.add(o); + } else { + result.add(t); + } + } + + this.dcontext = dcontext; + this.dxpath = dxpath; + this.dfirstonly = dfirstonly; + + this.dfiltered = Collections.unmodifiableList(filtered); + this.draw = Collections.unmodifiableList(raw); + this.dresult = Collections.unmodifiableList(result); + + } + + @Override + public Object getContext() { + return dcontext; + } + + @Override + public XPathExpression getXPathExpression() { + return dxpath; + } + + @Override + public List getResult() { + return dresult; + } + + @Override + public List getFilteredResults() { + return dfiltered; + } + + @Override + public List getRawResults() { + return draw; + } + + @Override + public boolean isFirstOnly() { + return dfirstonly; + } + + @Override + public String toString() { + return String.format("[XPathDiagnostic: '%s' evaluated (%s) against " + + "%s produced raw=%d discarded=%d returned=%d]", + dxpath.getExpression(), (dfirstonly ? "first" : "all"), + dcontext.getClass().getName(), draw.size(), dfiltered.size(), + dresult.size()); + } + +} diff --git a/core/src/java/org/jdom/xpath/util/package.html b/core/src/java/org/jdom/xpath/util/package.html new file mode 100644 index 0000000..e33bd4d --- /dev/null +++ b/core/src/java/org/jdom/xpath/util/package.html @@ -0,0 +1,3 @@ + + Classes useful for interfacing the JDOM XPath API to full XPath libraries. + diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/maven/maven.pom b/maven/maven.pom new file mode 100644 index 0000000..debc227 --- /dev/null +++ b/maven/maven.pom @@ -0,0 +1,86 @@ + + 4.0.0 + org.jdom + @artifactID@ + jar + + JDOM + @version@ + + + A complete, Java-based solution for accessing, manipulating, + and outputting XML data + + http://www.jdom.org + + + JDOM + http://www.jdom.org + + + + + JDOM-interest Mailing List + jdom-interest@jdom.org + http://jdom.markmail.org/ + + + + + + Similar to Apache License but with the acknowledgment clause removed + https://raw.github.com/hunterhacker/jdom/master/LICENSE.txt + repo + + + + + + git@github.com:/hunterhacker/jdom + scm:git:git@github.com:hunterhacker/jdom + scm:git:git@github.com:hunterhacker/jdom + + + + + hunterhacker + Jason Hunter + jhunter@servlets.com + + + rolfl + Rolf Lear + jdom@tuis.net + + + + + + jaxen + jaxen + 1.1.6 + true + + + xerces + xercesImpl + 2.11.0 + true + + + xalan + xalan + 2.7.2 + true + + + + + + @jdk@ + + \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..dd8321e --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include 'core', 'test', 'contrib' diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000..cb9f69b --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,2 @@ +/build +/out \ No newline at end of file diff --git a/test/README.txt b/test/README.txt new file mode 100644 index 0000000..05a92ac --- /dev/null +++ b/test/README.txt @@ -0,0 +1,12 @@ +Introduction +============ + +this 'test' folder is the home for a set of test cases covering all aspects +of JDOM behavior, used to ensure the JDOM core library consistently behaves as +documented. My expectation is that we'll run these tests before every +check-in. Currently the module includes a set of JUnit library. Your help in +fleshing out the test cases is most welcome. Send patches to the +jdom-interest list. + +You can run the test cases with 'ant junit' in the parent directory. + diff --git a/test/build.gradle b/test/build.gradle new file mode 100644 index 0000000..8f4fa4a --- /dev/null +++ b/test/build.gradle @@ -0,0 +1,22 @@ +dependencies { + compile project(':core') + testCompile project(':contrib') + testCompile 'junit:junit:4.12' + testRuntime 'jaxen:jaxen:1.1.6' +} + +sourceSets { + test { + java { + srcDir 'src/java' + } + resources { + srcDir 'src/resources' + } + resources { + srcDir 'src/java' + include '**/*.xml', '**/*.xsd', '**/*.dtd' + } + } +} + diff --git a/test/src/java/org/jdom/TestIllegalAddExceptn.java b/test/src/java/org/jdom/TestIllegalAddExceptn.java new file mode 100644 index 0000000..505d8db --- /dev/null +++ b/test/src/java/org/jdom/TestIllegalAddExceptn.java @@ -0,0 +1,111 @@ +package org.jdom; + +import static org.junit.Assert.*; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestIllegalAddExceptn { + + @Test + public void testIllegalAddExceptionElementAttributeString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + fac.attribute("att", "val"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementElementString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt1"), + fac.element("emt2"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementProcessingInstructionString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + fac.processingInstruction("target", "data"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionProcessingInstructionString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.processingInstruction("target", "data"), + "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementCommentString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + fac.comment("no comment!"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementCDATAString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + fac.cdata("cdata"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementTextString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + fac.text("text"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionCommentString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.comment("no comment!"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementEntityRefString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + fac.entityRef("name"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionElementNamespaceString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException(fac.element("emt"), + Namespace.getNamespace("prefix", "ns"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionDocTypeString() { + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertTrue (null != + new IllegalAddException( + fac.docType("emt"), "msg").getMessage()); + } + + @Test + public void testIllegalAddExceptionString() { + assertTrue (null != + new IllegalAddException("msg").getMessage()); + } + +} diff --git a/test/src/java/org/jdom/TestIllegalNameExceptn.java b/test/src/java/org/jdom/TestIllegalNameExceptn.java new file mode 100644 index 0000000..f7f5a26 --- /dev/null +++ b/test/src/java/org/jdom/TestIllegalNameExceptn.java @@ -0,0 +1,28 @@ +package org.jdom; + +import static org.junit.Assert.*; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestIllegalNameExceptn { + + @Test + public void testIllegalNameExceptionStringStringString() { + assertTrue (null != + new IllegalNameException("name", "construct", "reason").getMessage()); + } + + @Test + public void testIllegalNameExceptionStringString() { + assertTrue (null != + new IllegalNameException("name", "construct").getMessage()); + } + + @Test + public void testIllegalNameExceptionString() { + assertTrue (null != + new IllegalNameException("reason").getMessage()); + } + +} diff --git a/test/src/java/org/jdom/TestIllegalTargetExceptn.java b/test/src/java/org/jdom/TestIllegalTargetExceptn.java new file mode 100644 index 0000000..b3dd1fe --- /dev/null +++ b/test/src/java/org/jdom/TestIllegalTargetExceptn.java @@ -0,0 +1,23 @@ +package org.jdom; + +import static org.junit.Assert.*; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestIllegalTargetExceptn { + + @Test + public void testIllegalTargetExceptionStringString() { + assertTrue (null != + new IllegalTargetException("target", "reason").getMessage()); + + } + + @Test + public void testIllegalTargetExceptionString() { + assertTrue (null != + new IllegalTargetException("reason").getMessage()); + } + +} diff --git a/test/src/java/org/jdom/TestStringBin.java b/test/src/java/org/jdom/TestStringBin.java new file mode 100644 index 0000000..ee08266 --- /dev/null +++ b/test/src/java/org/jdom/TestStringBin.java @@ -0,0 +1,123 @@ +package org.jdom; + +import static org.junit.Assert.*; + +import org.jdom.test.util.UnitTestUtil; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestStringBin { + + // The following 4 all have the same hashcode 31776 ..... clever... huh? + private static final String[] samehc = new String[] { + "\u03FA\u00DA", + "\u0401\u0001", + "\u0400\u0020", + "\u03FF\u003F", + " ", + "\u03FE\u005E", + "\u03FD\u007D", + "\u03FC\u009C", + "\u03FB\u00BB", + }; + + @Test + public void testNegativeCapacity() { + try { + new StringBin(-1); + fail("excpect exception!"); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testSmallCapacity() { + assertNotNull(new StringBin(1)); + } + + @Test + public void testNull() { + StringBin bin = new StringBin(); + assertNull(bin.reuse(null)); + } + + @Test + public void testSameHashCode() { + for (int i = 0; i < samehc.length; i++) { + int hc = samehc[i].hashCode(); + if (hc != 31776) { + fail("Missed '" + samehc[i] + "' -> " + samehc[i].hashCode()); + } + } + StringBin bin = new StringBin(); + String[] actuals = new String[samehc.length]; + for (int i = 0; i < samehc.length; i++) { + actuals[i] = bin.reuse(samehc[i]); + assertEquals(samehc[i], actuals[i]); + // should have compacted things. + assertTrue(samehc[i] != actuals[i]); + } + assertTrue(bin.size() == samehc.length); + for (int i = 0; i < samehc.length; i++) { + assertTrue(actuals[i] == bin.reuse(samehc[i])); + } + assertTrue(bin.size() == samehc.length); + + + bin = new StringBin(); + for (int i = samehc.length - 1; i >= 0; i--) { + actuals[i] = bin.reuse(samehc[i]); + assertEquals(samehc[i], actuals[i]); + assertTrue(samehc[i] != actuals[i]); + } + for (int i = 0; i < samehc.length; i++) { + assertTrue(actuals[i] == bin.reuse(samehc[i])); + } + } + + @Test + public final void bulkIntern() { + String[] tstvals = new String[1024 * 256]; + String[] actvals = new String[tstvals.length]; + String[] dupvals = new String[tstvals.length]; + StringBin bin = new StringBin(2048); + int hc = 0; + for (int i = 0; i < tstvals.length; i++) { + tstvals[i] = "value " + i; + hc ^= tstvals[i].hashCode(); + dupvals[i] = "value " + i; + hc ^= dupvals[i].hashCode(); + } + assertTrue(hc == 0); + for (int i = 0; i < tstvals.length; i++) { + actvals[i] = bin.reuse(tstvals[i]); + assertTrue(tstvals[i] != actvals[i]); + assertEquals(tstvals[i], actvals[i]); + } + assertTrue(bin.size() == tstvals.length); + + for (int i = 0; i < tstvals.length; i++) { + assertTrue(actvals[i] == bin.reuse(tstvals[i])); + } + assertTrue(bin.size() == tstvals.length); + + for (int i = 0; i < tstvals.length; i++) { + assertTrue(actvals[i] == bin.reuse(dupvals[i])); + } + assertTrue(bin.size() == tstvals.length); + + String[] acthc = new String[samehc.length]; + for (int i = 0; i < samehc.length; i++) { + acthc[i] = bin.reuse(samehc[i]); + assertTrue(acthc[i] != samehc[i]); + assertEquals(samehc[i], acthc[i]); + } + assertTrue(bin.size() == tstvals.length + samehc.length); + for (int i = 0; i < samehc.length; i++) { + assertTrue(acthc[i] == bin.reuse(samehc[i])); + } + assertTrue(bin.size() == tstvals.length + samehc.length); + } + +} diff --git a/test/src/java/org/jdom/input/sax/TestTextBuffer.java b/test/src/java/org/jdom/input/sax/TestTextBuffer.java new file mode 100644 index 0000000..8843742 --- /dev/null +++ b/test/src/java/org/jdom/input/sax/TestTextBuffer.java @@ -0,0 +1,32 @@ +package org.jdom.input.sax; + +import static org.junit.Assert.*; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestTextBuffer { + + @Test + public void testIsAllWhitespace() { + TextBuffer tb = new TextBuffer(); + tb.append(" ".toCharArray(), 0, 3); + assertTrue(tb.isAllWhitespace()); + tb.append("frodo".toCharArray(), 0, 4); + assertFalse(tb.isAllWhitespace()); + } + + @Test + public void testToString() { + // this tests the expansion of the backing array. + final StringBuilder sb = new StringBuilder(); + final TextBuffer tb = new TextBuffer(); + final char[] data = "frodo".toCharArray(); + for (int i = 1000; i >= 0; i--) { + sb.append(data); + tb.append(data, 0, data.length); + assertEquals(sb.toString(), tb.toString()); + } + } + +} diff --git a/test/src/java/org/jdom/test/Test.properties b/test/src/java/org/jdom/test/Test.properties new file mode 100644 index 0000000..2cbd654 --- /dev/null +++ b/test/src/java/org/jdom/test/Test.properties @@ -0,0 +1,2 @@ +test.resourceRoot=./test/resources +test.scratchDirectory=./build/tmp diff --git a/test/src/java/org/jdom/test/cases/AbstractTestJDOMFactory.java b/test/src/java/org/jdom/test/cases/AbstractTestJDOMFactory.java new file mode 100644 index 0000000..85184c9 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/AbstractTestJDOMFactory.java @@ -0,0 +1,393 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.located.Located; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public abstract class AbstractTestJDOMFactory { + + private final boolean located; + + public AbstractTestJDOMFactory(boolean located) { + this.located = located; + } + + protected abstract JDOMFactory buildFactory(); + + private void checkLocated(final Content c) { + if (located) { + assertTrue("Expected " + c + " to be Located", c instanceof Located); + Located loc = (Located)c; + assertTrue(1 == loc.getLine()); + assertTrue(2 == loc.getColumn()); + } else { + assertFalse(c instanceof Located); + } + } + + @Test + public void testCdata() { + CDATA cdata = buildFactory().cdata("foo"); + assertTrue(cdata != null); + assertTrue("foo".equals(cdata.getValue())); + + CDATA ldata = buildFactory().cdata(1,2,"foo"); + assertTrue(ldata != null); + assertTrue("foo".equals(ldata.getValue())); + checkLocated(ldata); + } + + @Test + public void testText() { + Text text = buildFactory().text("foo"); + assertTrue(text != null); + assertTrue("foo".equals(text.getValue())); + + Text ltext = buildFactory().text(1, 2, "foo"); + assertTrue(ltext != null); + assertTrue("foo".equals(ltext.getValue())); + checkLocated(ltext); + } + + @Test + public void testComment() { + Comment comment = buildFactory().comment("foo"); + assertTrue(comment != null); + assertTrue("foo".equals(comment.getText())); + + Comment lcomment = buildFactory().comment(1, 2, "foo"); + assertTrue(lcomment != null); + assertTrue("foo".equals(lcomment.getText())); + checkLocated(lcomment); + } + + @Test + public void testAttributeStringStringNamespace() { + Attribute att = buildFactory().attribute("att", "val", Namespace.getNamespace("p", "uri")); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("uri".equals(att.getNamespace().getURI())); + assertTrue(Attribute.UNDECLARED_TYPE == att.getAttributeType()); + + att = buildFactory().attribute("att", "val", (Namespace)null); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("".equals(att.getNamespace().getURI())); + assertTrue(Attribute.UNDECLARED_TYPE == att.getAttributeType()); + } + + @Test + public void testAttributeStringStringIntNamespace() { + @SuppressWarnings("deprecation") + Attribute att = buildFactory().attribute("att", "val", Attribute.ID_TYPE.ordinal(), Namespace.getNamespace("p", "uri")); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("uri".equals(att.getNamespace().getURI())); + assertTrue(Attribute.ID_TYPE == att.getAttributeType()); + + att = buildFactory().attribute("att", "val", Attribute.ID_TYPE, null); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("".equals(att.getNamespace().getURI())); + assertTrue(Attribute.ID_TYPE == att.getAttributeType()); + } + + + @Test + public void testAttributeStringStringAttributeTypeNamespace() { + Attribute att = buildFactory().attribute("att", "val", Attribute.ID_TYPE, Namespace.getNamespace("p", "uri")); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("uri".equals(att.getNamespace().getURI())); + assertTrue(Attribute.ID_TYPE == att.getAttributeType()); + + att = buildFactory().attribute("att", "val", Attribute.ID_TYPE, null); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("".equals(att.getNamespace().getURI())); + assertTrue(Attribute.ID_TYPE == att.getAttributeType()); + } + + @Test + public void testAttributeStringString() { + Attribute att = buildFactory().attribute("att", "val"); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("".equals(att.getNamespace().getURI())); + assertTrue(Attribute.UNDECLARED_TYPE == att.getAttributeType()); + } + + @Test + public void testAttributeStringStringInt() { + @SuppressWarnings("deprecation") + Attribute att = buildFactory().attribute("att", "val", Attribute.ID_TYPE.ordinal()); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("".equals(att.getNamespace().getURI())); + assertTrue(Attribute.ID_TYPE == att.getAttributeType()); + } + + @Test + public void testAttributeStringStringType() { + Attribute att = buildFactory().attribute("att", "val", Attribute.ID_TYPE); + assertTrue("att".equals(att.getName())); + assertTrue("val".equals(att.getValue())); + assertTrue("".equals(att.getNamespace().getURI())); + assertTrue(Attribute.ID_TYPE == att.getAttributeType()); + } + + @Test + public void testDocTypeStringStringString() { + DocType dt = buildFactory().docType("element", "public", "system"); + assertTrue("element".equals(dt.getElementName())); + assertTrue("public".equals(dt.getPublicID())); + assertTrue("system".equals(dt.getSystemID())); + + DocType ldt = buildFactory().docType(1, 2, "element", "public", "system"); + assertTrue("element".equals(ldt.getElementName())); + assertTrue("public".equals(ldt.getPublicID())); + assertTrue("system".equals(ldt.getSystemID())); + checkLocated(ldt); + } + + @Test + public void testDocTypeStringString() { + DocType dt = buildFactory().docType("element", "system"); + assertTrue("element".equals(dt.getElementName())); + assertTrue(null == dt.getPublicID()); + assertTrue("system".equals(dt.getSystemID())); + DocType ldt = buildFactory().docType(1, 2, "element", "system"); + assertTrue("element".equals(ldt.getElementName())); + assertTrue(null == ldt.getPublicID()); + assertTrue("system".equals(ldt.getSystemID())); + checkLocated(ldt); + } + + @Test + public void testDocTypeString() { + DocType dt = buildFactory().docType("element"); + assertTrue("element".equals(dt.getElementName())); + assertTrue(null == dt.getPublicID()); + assertTrue(null == dt.getSystemID()); + + DocType ldt = buildFactory().docType(1, 2, "element"); + assertTrue("element".equals(ldt.getElementName())); + assertTrue(null == ldt.getPublicID()); + assertTrue(null == ldt.getSystemID()); + checkLocated(ldt); + } + + @Test + public void testElementStringNamespace() { + Element root = buildFactory().element("root", Namespace.getNamespace("uri")); + assertEquals("root", root.getName()); + assertEquals("uri", root.getNamespace().getURI()); + + Namespace n = null; + root = buildFactory().element("root", n); + assertEquals("root", root.getName()); + assertEquals("", root.getNamespace().getURI()); + + Element lroot = buildFactory().element(1, 2, "root", Namespace.getNamespace("uri")); + checkLocated(lroot); + + } + + @Test + public void testElementString() { + Element root = buildFactory().element("root"); + assertEquals("root", root.getName()); + assertEquals("", root.getNamespace().getURI()); + + Element lroot = buildFactory().element(1, 2, "root"); + checkLocated(lroot); + } + + @Test + public void testElementStringString() { + Element root = buildFactory().element("root", "uri"); + assertEquals("root", root.getName()); + assertEquals("uri", root.getNamespace().getURI()); + + Element lroot = buildFactory().element(1, 2, "root", "uri"); + checkLocated(lroot); + } + + @Test + public void testElementStringStringString() { + Element root = buildFactory().element("root", "p", "uri"); + assertEquals("root", root.getName()); + assertEquals("p", root.getNamespace().getPrefix()); + assertEquals("uri", root.getNamespace().getURI()); + + Element lroot = buildFactory().element(1, 2, "root", "p", "uri"); + checkLocated(lroot); + } + + @Test + public void testProcessingInstructionString() { + ProcessingInstruction pi = buildFactory().processingInstruction("target"); + assertEquals("target", pi.getTarget()); + assertEquals("", pi.getData()); + + ProcessingInstruction lpi = buildFactory().processingInstruction(1, 2, "target"); + checkLocated(lpi); + } + + @Test + public void testProcessingInstructionStringMap() { + Map data = new HashMap(); + data.put("key", "val"); + ProcessingInstruction pi = buildFactory().processingInstruction("target", data); + assertEquals("target", pi.getTarget()); + assertEquals("key=\"val\"", pi.getData()); + + ProcessingInstruction lpi = buildFactory().processingInstruction(1, 2, "target", data); + checkLocated(lpi); + } + + @Test + public void testProcessingInstructionStringString() { + ProcessingInstruction pi = buildFactory().processingInstruction("target", "data"); + assertEquals("target", pi.getTarget()); + assertEquals("data", pi.getData()); + + ProcessingInstruction lpi = buildFactory().processingInstruction(1, 2, "target", "data"); + checkLocated(lpi); + } + + @Test + public void testEntityRefString() { + EntityRef er = buildFactory().entityRef("name"); + assertEquals("name", er.getName()); + assertEquals(null, er.getPublicID()); + assertEquals(null, er.getSystemID()); + + EntityRef ler = buildFactory().entityRef(1, 2, "name"); + checkLocated(ler); + } + + @Test + public void testEntityRefStringStringString() { + EntityRef er = buildFactory().entityRef("name", "public", "system"); + assertEquals("name", er.getName()); + assertEquals("public", er.getPublicID()); + assertEquals("system", er.getSystemID()); + + EntityRef ler = buildFactory().entityRef(1, 2, "name", "public", "system"); + checkLocated(ler); + } + + @Test + public void testEntityRefStringString() { + EntityRef er = buildFactory().entityRef("name", "system"); + assertEquals("name", er.getName()); + assertEquals(null, er.getPublicID()); + assertEquals("system", er.getSystemID()); + + EntityRef ler = buildFactory().entityRef(1, 2, "name", "system"); + checkLocated(ler); + } + + @Test + public void testDocumentElementDocTypeString() { + JDOMFactory fac = buildFactory(); + Element root = fac.element("root"); + DocType dt = fac.docType("root"); + Document doc = buildFactory().document(root, dt, "baseuri"); + assertTrue(doc.getRootElement() == root); + assertTrue(doc.getDocType() == dt); + assertEquals("baseuri", doc.getBaseURI()); + + root.detach(); + dt.detach(); + + root = null; + doc = buildFactory().document(root, dt, "baseuri"); + assertFalse(doc.hasRootElement()); + assertTrue(doc.getDocType() == dt); + assertEquals("baseuri", doc.getBaseURI()); + } + + @Test + public void testDocumentElementDocType() { + JDOMFactory fac = buildFactory(); + Element root = fac.element("root"); + DocType dt = fac.docType("root"); + Document doc = buildFactory().document(root, dt); + assertTrue(doc.getRootElement() == root); + assertTrue(doc.getDocType() == dt); + assertEquals(null, doc.getBaseURI()); + } + + @Test + public void testDocumentElement() { + JDOMFactory fac = buildFactory(); + Element root = fac.element("root"); + Document doc = buildFactory().document(root); + assertTrue(doc.getRootElement() == root); + assertTrue(doc.getDocType() == null); + assertEquals(null, doc.getBaseURI()); + } + + @Test + public void testAddContent() { + JDOMFactory fac = buildFactory(); + Element root = fac.element("root"); + fac.addContent(root, new Text("foo")); + assertEquals(root.getTextNormalize(), "foo"); + } + + @Test + public void testSetAttribute() { + JDOMFactory fac = buildFactory(); + Element root = fac.element("root"); + fac.setAttribute(root, fac.attribute("att", "val")); + assertEquals(root.getAttributeValue("att"), "val"); + } + + @Test + public void testAddNamespaceDeclaration() { + JDOMFactory fac = buildFactory(); + Element root = fac.element("root"); + Namespace nsa = Namespace.getNamespace("p", "uri"); + fac.addNamespaceDeclaration(root, nsa); + assertTrue(root.getAdditionalNamespaces().contains(nsa)); + Namespace nsb = Namespace.getNamespace("p", "uri"); + fac.addNamespaceDeclaration(root, nsb); + assertTrue(root.getAdditionalNamespaces().contains(nsb)); + assertTrue(root.getAdditionalNamespaces().contains(nsa)); + } + + @Test + public void testSetRoot() { + JDOMFactory fac = buildFactory(); + Document doc = fac.document(null); + Element root = fac.element("root"); + assertFalse(doc.hasRootElement()); + assertTrue(root.getParent() == null); + fac.setRoot(doc, root); + assertTrue(doc.hasRootElement()); + assertTrue(doc.getRootElement() == root); + assertTrue(root.getParent() == doc); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestAttribute.java b/test/src/java/org/jdom/test/cases/TestAttribute.java new file mode 100644 index 0000000..7b767f1 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestAttribute.java @@ -0,0 +1,958 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.DataConversionException; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.IllegalDataException; +import org.jdom.IllegalNameException; +import org.jdom.Namespace; +import org.jdom.test.util.UnitTestUtil; + +/** + * Test the expected behaviour of the Attribute class. + * + * @author unascribed + * @version 0.1 + */ +@SuppressWarnings("javadoc") +public final class TestAttribute { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (final String args[]) { + JUnitCore.runClasses(TestAttribute.class); + } + + /** + * Test the simple case of constructing an attribute without name, value, + * namespace or prefix + */ + @Test + public void test_TCC() { + final Attribute attribute = new Attribute(){ + private static final long serialVersionUID = 200L; + }; + assertTrue(null == attribute.getName()); + assertTrue(attribute.isSpecified()); + } + + /** + * Test the simple case of constructing an attribute without + * namespace or prefix + */ + @Test + public void test_TCC___String_String() { + final Attribute attribute = new Attribute("test", "value"); + assertTrue(attribute.isSpecified()); + assertTrue("incorrect attribute name", attribute.getName().equals("test")); + assertTrue("incoorect attribute value", attribute.getValue().equals("value")); + assertEquals("incorrect attribute type", attribute.getAttributeType(), Attribute.UNDECLARED_TYPE); + + //should have been put in the NO_NAMESPACE namespace + assertTrue("incorrect namespace", attribute.getNamespace().equals(Namespace.NO_NAMESPACE)); + + + try { + new Attribute(null, "value"); + fail("didn't catch null attribute name"); + } catch (final NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + new Attribute("test", null); + fail("didn't catch null attribute value"); + } catch (final NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + new Attribute("test" + (char)0x01, "value"); + fail("didn't catch invalid attribute name"); + } catch (final IllegalArgumentException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + new Attribute("test", "test" + (char)0x01); + fail("didn't catch invalid attribute value"); + } catch (final IllegalArgumentException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + } + + /** + * Test the constructor with name, value and namespace + */ + @Test + public void test_TCC___String_String_OrgJdomNamespace() { + { + final Namespace namespace = Namespace.getNamespace("prefx", "http://some.other.place"); + + final Attribute attribute = new Attribute("test", "value", namespace); + assertTrue("incorrect attribute name", attribute.getName().equals("test")); + assertTrue("incoorect attribute value", attribute.getValue().equals("value")); + assertTrue("incorrect Namespace", attribute.getNamespace().equals(namespace)); + + assertEquals("incoorect attribute type", attribute.getAttributeType(), Attribute.UNDECLARED_TYPE); + } + + //now test that the attribute cannot be created with a namespace + //without a prefix + final Namespace defaultNamespace = Namespace.getNamespace("http://some.other.place"); + try { + new Attribute("test", "value", defaultNamespace); + fail("allowed creation of attribute with a default namespace"); + } catch (final IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + new Attribute("test", "value", (Namespace)null); + } catch (Exception e) { + fail("didn't handle null attribute namespace"); + } + } + + + /** + * Test the constructor with name, value and namespace + */ + @Test + public void test_TCC___String_String_int() { + { + + @SuppressWarnings("deprecation") + final Attribute attribute = new Attribute("test", "value", AttributeType.ID.ordinal()); + assertTrue("incorrect attribute name", attribute.getName().equals("test")); + assertTrue("incoorect attribute value", attribute.getValue().equals("value")); + assertTrue("incorrect Namespace", attribute.getNamespace().equals(Namespace.NO_NAMESPACE)); + + assertEquals("incoorect attribute type", attribute.getAttributeType(), Attribute.ID_TYPE); + } + + } + + + /** + * Test the constructor with name, value and namespace + */ + @Test + public void test_TCC___String_String_int_OrgJdomNamespace() { + { + final Namespace namespace = Namespace.getNamespace("prefx", "http://some.other.place"); + + @SuppressWarnings("deprecation") + final Attribute attribute = new Attribute("test", "value", Attribute.ID_TYPE.ordinal(), namespace); + assertTrue("incorrect attribute name", attribute.getName().equals("test")); + assertTrue("incoorect attribute value", attribute.getValue().equals("value")); + assertTrue("incorrect Namespace", attribute.getNamespace().equals(namespace)); + + assertEquals("incoorect attribute type", attribute.getAttributeType(), Attribute.ID_TYPE); + } + + //now test that the attribute cannot be created with a namespace + //without a prefix + final Namespace defaultNamespace = Namespace.getNamespace("http://some.other.place"); + try { + new Attribute("test", "value", defaultNamespace); + fail("allowed creation of attribute with a default namespace"); + } catch (final IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + new Attribute("test", "value", (Namespace)null); + } catch (Exception e) { + fail("didn't handle null attribute namespace"); + } + } + + + /** + * Test possible attribute values + */ + @SuppressWarnings("deprecation") + @Test + public void test_TCM__Attribute_setAttributeType_int() { + final Attribute attribute = new Attribute("test", "value"); + + for(int attributeType = -10; attributeType < 15; attributeType++) { + if ( + Attribute.UNDECLARED_TYPE.ordinal() <= attributeType && + attributeType <= Attribute.ENUMERATED_TYPE.ordinal() + ) { + attribute.setAttributeType(attributeType); + assertTrue(attribute.getAttributeType().ordinal() == attributeType); + continue; + } + try { + attribute.setAttributeType(attributeType); + fail("set unvalid attribute type: "+ attributeType); + } + catch(final IllegalDataException ignore) { + // is expected + } + catch(final Exception exception) { + exception.printStackTrace(); + fail("unknown exception throws: "+ exception); + } + } + } + + /** + * Test a simple object comparison + */ + @Test + public void test_TCM__boolean_equals_Object() { + final Attribute attribute = new Attribute("test", "value"); + + assertFalse("attribute equal to null", attribute.equals(null)); + + final Object object = attribute; + assertTrue("object not equal to attribute", attribute.equals(object)); + assertTrue("attribute not equal to object", object.equals(attribute)); + + // current implementation checks only for identity +// final Attribute clonedAttribute = (Attribute) attribute.clone(); +// assertTrue("attribute not equal to its clone", attribute.equals(clonedAttribute)); +// assertTrue("clone not equal to attribute", clonedAttribute.equals(attribute)); + } + + /** + * test the convienience method getBooleanValue(); + */ + @Test + public void test_TCM__boolean_getBooleanValue() { + final Attribute attribute = new Attribute("test", "true"); + try { + assertTrue("incorrect boolean true value", attribute.getBooleanValue()); + + attribute.setValue("false"); + assertTrue("incorrect boolean false value", !attribute.getBooleanValue()); + + attribute.setValue("TRUE"); + assertTrue("incorrect boolean TRUE value", attribute.getBooleanValue()); + + attribute.setValue("FALSE"); + assertTrue("incorrect boolean FALSE value", !attribute.getBooleanValue()); + + attribute.setValue("On"); + assertTrue("incorrect boolean TRUE value", attribute.getBooleanValue()); + + attribute.setValue("Yes"); + assertTrue("incorrect boolean TRUE value", attribute.getBooleanValue()); + + attribute.setValue("1"); + assertTrue("incorrect boolean TRUE value", attribute.getBooleanValue()); + + attribute.setValue("OfF"); + assertTrue("incorrect boolean FALSE value", !attribute.getBooleanValue()); + + attribute.setValue("0"); + assertTrue("incorrect boolean FALSE value", !attribute.getBooleanValue()); + + attribute.setValue("No"); + assertTrue("incorrect boolean FALSE value", !attribute.getBooleanValue()); + + } catch (DataConversionException e) { + fail("couldn't convert boolean value"); + } + + try { + attribute.setValue("foo"); + assertTrue("incorrectly returned boolean from non boolean value", attribute.getBooleanValue()); + + } catch (DataConversionException e) { + // good + } catch (Exception e) { + fail("Expected DataConversionException, but got " + e.getClass().getName()); + } + + + } + /** + * Test convience method for getting doubles from an Attribute + */ + @Test + public void test_TCM__double_getDoubleValue() { + Attribute attr = new Attribute("test", "11111111111111"); + try { + assertTrue("incorrect double value", attr.getDoubleValue() == 11111111111111d ); + + attr.setValue("0"); + assertTrue("incorrect double value", attr.getDoubleValue() == 0 ); + + attr.setValue(Double.toString(java.lang.Double.MAX_VALUE)); + assertTrue("incorrect double value", attr.getDoubleValue() == java.lang.Double.MAX_VALUE); + + attr.setValue(Double.toString(java.lang.Double.MIN_VALUE)); + assertTrue("incorrect double value", attr.getDoubleValue() == java.lang.Double.MIN_VALUE); + + } catch (DataConversionException e) { + fail("couldn't convert boolean value"); + } + + try { + attr.setValue("foo"); + fail("incorrectly returned double from non double value" + attr.getDoubleValue()); + + } catch (DataConversionException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + } + + /** + * Test floats returned from Attribute values. Checks that both correctly + * formatted (java style) and incorrectly formatted float strings are + * returned or raise a DataConversionException + */ + @Test + public void test_TCM__float_getFloatValue() { + Attribute attr = new Attribute("test", "1.00000009999e+10f"); + float flt = 1.00000009999e+10f; + try { + assertTrue("incorrect float conversion", + attr.getFloatValue() == flt); + } catch (DataConversionException e) { + fail("couldn't convert to float"); + } + + // test an invalid float + + attr.setValue("1.00000009999e"); + try { + attr.getFloatValue(); + fail("incorrect float conversion from non float"); + } catch (DataConversionException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + } + + /** + * Tests that Attribute can convert value strings to ints and + * that is raises DataConversionException if it is not an int. + */ + @Test + public void test_TCM__int_getIntValue() { + final Attribute attribute = new Attribute("test", ""); + int summand = 3; + for(int i = 0; i < 28; i++) { + summand <<= 1; + final int value = Integer.MIN_VALUE + summand; + + attribute.setValue("" + value); + try { + assertEquals("incorrect int conversion", attribute.getIntValue(), value); + } catch (final DataConversionException e) { + fail("couldn't convert to int"); + } + } + + //test an invalid int + TCM__int_getIntValue_invalidInt("" + Long.MIN_VALUE); + TCM__int_getIntValue_invalidInt("" + Long.MAX_VALUE); + TCM__int_getIntValue_invalidInt("" + Float.MIN_VALUE); + TCM__int_getIntValue_invalidInt("" + Float.MAX_VALUE); + TCM__int_getIntValue_invalidInt("" + Double.MIN_VALUE); + TCM__int_getIntValue_invalidInt("" + Double.MAX_VALUE); + } + + private void TCM__int_getIntValue_invalidInt(final String value) { + final Attribute attribute = new Attribute("test", value); + try { + attribute.getIntValue(); + fail("incorrect int conversion from non int"); + } catch (final DataConversionException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + /** + * Test that Attribute returns a valid hashcode. + */ + @Test + public void test_TCM__int_hashCode() { + final Attribute attr = new Attribute("test", "value"); + //only an exception would be a problem + int i = -1; + try { + i = attr.hashCode(); + } + catch(Exception e) { + fail("bad hashCode"); + } + final Attribute attr2 = new Attribute("test", "value"); + + //different Attributes, same text + final int x = attr2.hashCode(); + assertFalse("Different Attributes with same value have same hashcode", x == i); + + final Attribute attr3 = new Attribute("test2", "value"); + //only an exception would be a problem + final int y = attr3.hashCode(); + assertFalse("Different Attributes have same hashcode", y == x); + } + + /** + * Test the convienience method for returning a long from an Attribute + */ + @Test + public void test_TCM__long_getLongValue() { + final Attribute attribute = new Attribute("test", ""); + long summand = 3; + for(int i = 0; i < 60; i++) { + summand <<= 1; + final long value = Long.MIN_VALUE + summand; + + attribute.setValue("" + value); + try { + assertEquals("incorrect long conversion", attribute.getLongValue(), value); + } catch (final DataConversionException e) { + fail("couldn't convert to long"); + } + } + + //test an invalid long + attribute.setValue("100000000000000000000000000"); + try { + attribute.getLongValue(); + fail("incorrect long conversion from non long"); + } catch (final DataConversionException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test that an Attribute can clone itself correctly. The test + * covers the simple case and with the attribute using a namespace + * and prefix. + */ + @Test + public void test_TCM__Object_clone() { + TCM__Object_clone__default(); + + TCM__Object_clone__attributeType(Attribute.UNDECLARED_TYPE); + TCM__Object_clone__attributeType(Attribute.CDATA_TYPE); + TCM__Object_clone__attributeType(Attribute.ID_TYPE); + TCM__Object_clone__attributeType(Attribute.IDREF_TYPE); + TCM__Object_clone__attributeType(Attribute.IDREFS_TYPE); + TCM__Object_clone__attributeType(Attribute.ENTITY_TYPE); + TCM__Object_clone__attributeType(Attribute.ENTITIES_TYPE); + TCM__Object_clone__attributeType(Attribute.NMTOKEN_TYPE); + TCM__Object_clone__attributeType(Attribute.NMTOKENS_TYPE); + TCM__Object_clone__attributeType(Attribute.NOTATION_TYPE); + TCM__Object_clone__attributeType(Attribute.ENUMERATED_TYPE); + + + TCM__Object_clone__Namespace_default(); + + TCM__Object_clone__Namespace_attributeType(Attribute.UNDECLARED_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.CDATA_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.ID_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.IDREF_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.IDREFS_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.ENTITY_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.ENTITIES_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.NMTOKEN_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.NMTOKENS_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.NOTATION_TYPE); + TCM__Object_clone__Namespace_attributeType(Attribute.ENUMERATED_TYPE); + } + + /** + * Test that an Attribute can clone itself correctly. The test covers the + * simple case with only name and value. + */ + private void TCM__Object_clone__default() { + final String attributeName = "test"; + final String attributeValue = "value"; + + final Attribute attribute = new Attribute(attributeName, attributeValue); + final Attribute clonedAttribute = attribute.clone(); + + assertTrue("incorrect name in clone", clonedAttribute.getName().equals(attributeName)); + assertTrue("incorrect value in clone", clonedAttribute.getValue().equals(attributeValue)); + assertEquals("incoorect attribute type in clone", clonedAttribute.getAttributeType(), Attribute.UNDECLARED_TYPE); + } + + /** + * Test that an Attribute can clone itself correctly. The test covers the + * simple case with only name, value and a given attribute type. + */ + private void TCM__Object_clone__attributeType(final AttributeType attributeType) { + final String attributeName = "test"; + final String attributeValue = "value"; + + final Attribute attribute = new Attribute(attributeName, attributeValue, attributeType); + final Attribute clonedAttribute = attribute.clone(); + + assertTrue("incorrect name in clone", clonedAttribute.getName().equals(attributeName)); + assertTrue("incorrect value in clone", clonedAttribute.getValue().equals(attributeValue)); + assertEquals("incoorect attribute type in clone", clonedAttribute.getAttributeType(), attributeType); + } + + /** + * Test that an Attribute can clone itself correctly. The test covers the + * case with name, value, prefix, namespace and a given attribute type. + */ + private void TCM__Object_clone__Namespace_default() { + final String prefix = "prefx"; + final String uri = "http://some.other.place"; + + final Namespace namespace = Namespace.getNamespace(prefix, uri); + + final String attributeName = "test"; + final String attributeValue = "value"; + + final Attribute attribute = new Attribute(attributeName, attributeValue, namespace); + final Attribute clonedAttribute = attribute.clone(); + + assertTrue("incorrect name in clone", clonedAttribute.getName().equals(attributeName)); + assertTrue("incorrect value in clone", clonedAttribute.getValue().equals(attributeValue)); + assertEquals("incoorect attribute type in clone", clonedAttribute.getAttributeType(), Attribute.UNDECLARED_TYPE); + + assertTrue("incorrect prefix in clone", clonedAttribute.getNamespacePrefix().equals(prefix)); + assertTrue("incorrect qualified name in clone", clonedAttribute.getQualifiedName().equals(prefix + ':' + attributeName)); + assertTrue("incorrect Namespace URI in clone", clonedAttribute.getNamespaceURI().equals(uri)); + } + + /** + * Test that an Attribute can clone itself correctly. The test covers the + * case with name, value, prefix, namespace and a given attribute type. + */ + private void TCM__Object_clone__Namespace_attributeType(final AttributeType attributeType) { + final String prefix = "prefx"; + final String uri = "http://some.other.place"; + + final Namespace namespace = Namespace.getNamespace(prefix, uri); + + final String attributeName = "test"; + final String attributeValue = "value"; + + final Attribute attribute = new Attribute(attributeName, attributeValue, attributeType, namespace); + final Attribute clonedAttribute = attribute.clone(); + + assertTrue("incorrect name in clone", clonedAttribute.getName().equals(attributeName)); + assertTrue("incorrect value in clone", clonedAttribute.getValue().equals(attributeValue)); + assertEquals("incoorect attribute type in clone", clonedAttribute.getAttributeType(), attributeType); + + assertTrue("incorrect prefix in clone", clonedAttribute.getNamespacePrefix().equals(prefix)); + assertTrue("incorrect qualified name in clone", clonedAttribute.getQualifiedName().equals(prefix + ':' + attributeName)); + assertTrue("incorrect Namespace URI in clone", clonedAttribute.getNamespaceURI().equals(uri)); + } + + /** + * Test that setting an Attribute's value works correctly. + */ + @Test + public void test_TCM__OrgJdomAttribute_setValue_String() { + final Namespace namespace = Namespace.getNamespace("prefx", "http://some.other.place"); + + final Attribute attribute = new Attribute("test", "value", namespace); + + assertTrue("incorrect value before set", attribute.getValue().equals("value")); + + attribute.setValue("foo"); + assertTrue("incorrect value after set", attribute.getValue().equals("foo")); + + //test that the verifier is called + try { + attribute.setValue(null); + fail("Attribute setValue didn't catch null string"); + } catch (final NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + final char c= 0x11; + final StringBuilder buffer = new StringBuilder("hhhh"); + buffer.setCharAt(2, c); + attribute.setValue(buffer.toString()); + fail("Attribute setValue didn't catch invalid comment string"); + } catch (final IllegalDataException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + } + + /** + * check that the attribute can return the correct parent element + */ + @Test + public void test_TCM__OrgJdomElement_getParent() { + final Attribute attribute = new Attribute("test", "value"); + assertNull("attribute returned parent when there was none", attribute.getParent()); + + final Element element = new Element("element"); + element.setAttribute(attribute); + + assertNotNull("no parent element found", attribute.getParent()); + + assertEquals("invalid parent element", attribute.getParent(), element); + } + + /** + * check that the attribute can return the correct document + */ + @Test + public void test_TCM__OrgJdomDocument_getDocument() { + final Attribute attribute = new Attribute("test", "value"); + assertNull("attribute returned document when there was none", attribute.getDocument()); + + final Element element = new Element("element"); + element.setAttribute(attribute); + assertNull("attribute returned document when there was none", attribute.getDocument()); + + final Document document = new Document(element); + + assertEquals("invalid document", attribute.getDocument(), document); + } + + /** + * Test that an independantly created Namespace and one + * retrieved from an Attribute create with the same namespace + * parameters are the same namespace. + */ + @Test + public void test_TCM__OrgJdomNamespace_getNamespace() { + final Namespace ns = Namespace.getNamespace("prefx", "http://some.other.place"); + + final Attribute attr = new Attribute("test", "value", ns); + final Namespace ns2 = Namespace.getNamespace("prefx", "http://some.other.place"); + + assertTrue("incorrect Namespace", attr.getNamespace().equals(ns2)); + + } + + /** + * Test that an Attribute returns it's correct name. + */ + @Test + public void test_TCM__String_getName() { + final Attribute attr = new Attribute("test", "value"); + assertTrue("incorrect attribute name", attr.getName().equals("test")); + + } + + /** + * Test that an Attribute returns the correct Namespace prefix. + */ + @Test + public void test_TCM__String_getNamespacePrefix() { + final Namespace namespace = Namespace.getNamespace("prefx", "http://some.other.place"); + + final Attribute attribute = new Attribute("test", "value", namespace); + assertTrue("incorrect prefix", attribute.getNamespacePrefix().equals("prefx")); + } + + /** + * Test that an Attribute returns the correct Namespace URI. + */ + @Test + public void test_TCM__String_getNamespaceURI() { + final Namespace namespace = Namespace.getNamespace("prefx", "http://some.other.place"); + + final Attribute attribute = new Attribute("test", "value", namespace); + assertTrue("incorrect URI", attribute.getNamespaceURI().equals("http://some.other.place")); + + } + + /** + * Tests that an Attribute returns the correct Qualified Name. + */ + @Test + public void test_TCM__String_getQualifiedName() { + final String prefix = "prefx"; + final String uri = "http://some.other.place"; + + final Namespace namespace = Namespace.getNamespace(prefix, uri); + + final String attributeName = "test"; + final String attributeQName = prefix + ':' + attributeName; + final String attributeValue = "value"; + + final Attribute qulifiedAttribute = new Attribute(attributeName, attributeValue, namespace); + assertEquals("incorrect qualified name", qulifiedAttribute.getQualifiedName(), attributeQName); + + final Attribute attribute = new Attribute(attributeName, attributeValue); + assertEquals("incorrect qualified name", attribute.getQualifiedName(), attributeName); + + } + /** + * Test that Attribute returns the correct serialized form. + */ + @Test + public void test_TCM__String_getSerializedForm() { + /* noop because the method is deprecated + + Namespace ns = Namespace.getNamespace("prefx", "http://some.other.place"); + Attribute attr = new Attribute("test", "value", ns); + String serialized = attr.getSerializedForm(); + assertTrue("incorrect serialized form", serialized.equals("prefx:test=\"value\"")); + */ + + } + + /** + * Test that an Attribute returns the correct value. + */ + @Test + public void test_TCM__String_getValue() { + final Namespace ns = Namespace.getNamespace("prefx", "http://some.other.place"); + final Attribute attr = new Attribute("test", "value", ns); + assertTrue("incorrect value", attr.getValue().equals("value")); + + } + + /** + * Test that the toString function works according to the + * JDOM spec. + */ + @Test + public void test_TCM__String_toString() { + //expected value + final Namespace ns = Namespace.getNamespace("prefx", "http://some.other.place"); + final Attribute attr = new Attribute("test", "value", ns); + String str= attr.toString(); + assertTrue("incorrect toString form", str.equals("[Attribute: prefx:test=\"value\"]")); + + } + + /** + * Test that the detach() function works according to the JDOM spec. + * + * @see Attribute#detach() + */ + @Test + public void test_TCM___detach() { + final Element element = new Element("element"); + element.setAttribute("name", "value"); + + final Attribute attribute = element.getAttribute("name"); + + // should never occur, just to be sure + assertNotNull("no attribute found", attribute); + + + assertNotNull("no parent for attribute found", attribute.getParent()); + + attribute.detach(); + + assertNull("attribute has still a parent", attribute.getParent()); + } + + @Test + public void serialization_default() { + final String attributeName = "test"; + final String attributeValue = "value"; + + final Attribute attribute = new Attribute(attributeName, attributeValue); + + final Attribute serializedAttribute = UnitTestUtil.deSerialize(attribute); + + assertTrue(attribute != serializedAttribute); + + assertTrue("incorrect name in serialized attribute", serializedAttribute.getName().equals(attributeName)); + assertTrue("incorrect value in serialized attribute", serializedAttribute.getValue().equals(attributeValue)); + assertEquals("incoorect attribute type in serialized attribute", serializedAttribute.getAttributeType(), Attribute.UNDECLARED_TYPE); + + assertEquals("incorrect Namespace in serialized attribute", serializedAttribute.getNamespace(), Namespace.NO_NAMESPACE); + } + + @Test + public void serialization_Namespace() { + final String prefix = "prefx"; + final String uri = "http://some.other.place"; + + final Namespace namespace = Namespace.getNamespace(prefix, uri); + + final String attributeName = "test"; + final String attributeValue = "value"; + + final Attribute attribute = new Attribute(attributeName, attributeValue, namespace); + + final Attribute serializedAttribute = UnitTestUtil.deSerialize(attribute); + + assertTrue("incorrect name in serialized attribute", serializedAttribute.getName().equals(attributeName)); + assertTrue("incorrect value in serialized attribute", serializedAttribute.getValue().equals(attributeValue)); + assertEquals("incoorect attribute type in serialized attribute", serializedAttribute.getAttributeType(), Attribute.UNDECLARED_TYPE); + + assertTrue("incorrect prefix in serialized attribute", serializedAttribute.getNamespacePrefix().equals(prefix)); + assertTrue("incorrect qualified name in serialized attribute", serializedAttribute.getQualifiedName().equals(prefix + ':' + attributeName)); + assertTrue("incorrect Namespace URI in serialized attribute", serializedAttribute.getNamespaceURI().equals(uri)); + + assertEquals("incorrect Namespace in serialized attribute", serializedAttribute.getNamespace(), namespace); + } + + @Test + public void testInfinity() throws DataConversionException { + Attribute infinity = new Attribute("name", "Infinity"); + Attribute neginfinity = new Attribute("name", "-Infinity"); + Attribute inf = new Attribute("name", "INF"); + Attribute neginf = new Attribute("name", "-INF"); + assertEquals(infinity.getDoubleValue(), Double.POSITIVE_INFINITY, 0.0); + assertEquals(neginfinity.getDoubleValue(), Double.NEGATIVE_INFINITY, 0.0); + assertEquals(inf.getDoubleValue(), Double.POSITIVE_INFINITY, 0.0); + assertEquals(neginf.getDoubleValue(), Double.NEGATIVE_INFINITY, 0.0); + } + + @Test + public void testDetatchCloneParentAttribute() { + Element parent = new Element("root"); + Attribute content = new Attribute("att", "val"); + parent.setAttribute(content); + Attribute clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + + @Test + public void testCloneDetatchParentAttribute() { + Element parent = new Element("root"); + Attribute content = new Attribute("att", "val"); + parent.setAttribute(content); + Attribute clone = content.clone(); + assertNull(clone.getParent()); + assertEquals(parent, content.getParent()); + assertEquals(content.getValue(), clone.getValue()); + content.detach(); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + + @Test + public void testNullAttributeType() { + Attribute att = new Attribute("att", "value", AttributeType.CDATA); + assertTrue(att.getAttributeType() == AttributeType.CDATA); + att.setAttributeType(null); + assertTrue(att.getAttributeType() == AttributeType.UNDECLARED); + att.setAttributeType(AttributeType.ID); + assertTrue(att.getAttributeType() == AttributeType.ID); + } + + @Test + public void testSpecified() { + Attribute att = new Attribute("att", "value"); + assertTrue(att.isSpecified()); + att.setSpecified(false); + assertFalse(att.isSpecified()); + + att.setValue("val"); + assertTrue(att.isSpecified()); + att.setSpecified(false); + assertFalse(att.isSpecified()); + + att.setName("attb"); + assertTrue(att.isSpecified()); + att.setSpecified(false); + assertFalse(att.isSpecified()); + + att.setNamespace(Namespace.getNamespace("pfx", "nothing")); + assertTrue(att.isSpecified()); + att.setSpecified(false); + assertFalse(att.isSpecified()); + + att.setAttributeType(AttributeType.ID); + assertTrue(att.isSpecified()); + att.setSpecified(false); + assertFalse(att.isSpecified()); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestAttributeList.java b/test/src/java/org/jdom/test/cases/TestAttributeList.java new file mode 100644 index 0000000..329da8a --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestAttributeList.java @@ -0,0 +1,412 @@ +package org.jdom.test.cases; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.Comment; +import org.jdom.Element; +import org.jdom.IllegalAddException; +import org.jdom.Namespace; +import org.jdom.Text; +import org.jdom.internal.ArrayCopy; +import org.jdom.test.util.AbstractTestList; + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; +import static org.jdom.test.util.UnitTestUtil.*; + +@SuppressWarnings("javadoc") +public class TestAttributeList extends AbstractTestList { + + private static final Element base = new Element("dummy"); + + + public TestAttributeList() { + super(Attribute.class, false); + } + + @Override + public List buildEmptyList() { + base.getAttributes().clear(); + return base.getAttributes(); + } + + @Override + public Attribute[] buildSampleContent() { + return new Attribute[]{ new Attribute("zero", "val"), + new Attribute("one", "val"), new Attribute("two", "val"), + new Attribute("three", "val"), new Attribute("four", "val"), + new Attribute("five", "val"), new Attribute("six", "val"), + new Attribute("att", "val", Namespace.getNamespace("pfx", "nsX")), + new Attribute("sec", "val", Namespace.getNamespace("pfx", "nsX")) + }; + } + + + + @Override + public Attribute[] buildAdditionalContent() { + return new Attribute[]{ new Attribute("seven", "val"), + new Attribute("eight", "val")}; + } + + @Override + public Object[] buildIllegalClassContent() { + Object[] ret = new Object[] { + new Text("Hello"), + new Comment("Hello!") + }; + return ret; + } + + @Override + public Attribute[] buildIllegalArgumentContent() { + // this is illegal because it redefines the namespace from uri nsY to nsZ + return new Attribute[]{ + new Attribute("att", "val", Namespace.getNamespace("pfx", "nsY")), + new Attribute("att", "val", Namespace.getNamespace("pfx", "nsZ")) + + }; + } + + @Before + public void detatchAll () { + // make sure all content is detatched before each test. + for (Attribute c : buildSampleContent()) { + c.detach(); + } + } + + @Test + public void testDuplicateAttribute() { + Element emt = new Element("mine"); + List attlist = emt.getAttributes(); + Attribute att = new Attribute("hi", "there"); + Attribute frodo = new Attribute("hi", "frodo"); + Attribute bilbo = new Attribute("boo", "bilbo"); + attlist.add(att); + try { + Element e2 = new Element("gandalph"); + e2.setAttribute(frodo); + attlist.add(frodo); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + frodo.detach(); + + // adding one 'hi' attribute should displace the other. + assertTrue(att.getParent() == emt); + assertTrue(attlist.add(frodo)); + assertTrue(att.getParent() == null); + assertTrue(attlist.add(att)); + assertTrue(att.getParent() == emt); + assertTrue(att == att.detach()); + + // list is now empty. + assertTrue(attlist.isEmpty()); + assertTrue(attlist.add(frodo)); + assertTrue(attlist.add(bilbo)); + assertTrue(frodo == attlist.set(0, att)); + + try { + attlist.add(attlist.size(), frodo); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + try { + attlist.set(1, frodo); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + } + + @Test + public void testAttributeNamspaceCollision() { + Element emt = new Element("mine"); + List attlist = emt.getAttributes(); + Attribute atta = new Attribute("hi", "there", Namespace.getNamespace("mypfx", "nsa")); + Attribute attb = new Attribute("hi", "there", Namespace.getNamespace("mypfx", "nsb")); + attlist.add(atta); + try { + // cannot add two different namespaces with same prefix. + attlist.add(attb); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + Attribute attc = new Attribute("bilbo", "baggins", Namespace.getNamespace("mypfc", "nsc")); + // can add a different prefix. + attlist.add(attc); + try { + // cannot set an attribute that causes a conflict. + attlist.set(1, attb); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + // but you can swap an existing attribute with a different one that changes + // the namespace URI for a prefix. + attlist.set(0, attb); + + } + + @Test + public void testSetAttributes() { + final Attribute[] extra = buildAdditionalContent(); + if (extra.length <= 0) { + // android + //Assume.assumeTrue(extra.length > 0); + return; + } + final Attribute[] content = buildSampleContent(); + if (content.length <= 0) { + // android + // Assume.assumeTrue(content.length > 0); + return; + } + + // populate the list. + List list = buildEmptyList(); + assertTrue(list.addAll(0, Arrays.asList(content))); + quickCheck(list, content); + + // OK, we have a list of attributes.... behind the scenes, we have an + // an Element too... we need the element to get the setAttributes() + // method which in turn accesses the clearAndSet(). + Element myelement = list.get(0).getParent(); + assertNotNull(myelement); + + ArrayList toset = new ArrayList(extra.length); + toset.addAll(Arrays.asList(extra)); + + // OK, test the setAttributes first. + assertTrue(myelement == myelement.setAttributes(toset)); + // attributes should be the new ones. + quickCheck(list, extra); + // restore the old ones. + assertTrue(myelement == myelement.setAttributes(Arrays.asList(content))); + // ensure an empty list clears... + toset.clear(); + assertTrue(myelement == myelement.setAttributes(toset)); + assertTrue(list.isEmpty()); + // restore the old ones. + assertTrue(myelement == myelement.setAttributes(Arrays.asList(content))); + // ensure a null list clears... + toset = null; + assertTrue(myelement == myelement.setAttributes(toset)); + assertTrue(list.isEmpty()); + + + } + + + @Test + public void testIllegalSetAttributes() { + final Attribute[] illegal = buildIllegalArgumentContent(); + if (illegal.length <= 0) { + //Assume.assumeTrue(illegal.length > 0); + return; + } + final Attribute[] extra = buildAdditionalContent(); + if (extra.length <= 0) { + // Assume.assumeTrue(extra.length > 0); + return; + } + final Attribute[] content = buildSampleContent(); + if (content.length <= 0) { + //Assume.assumeTrue(content.length > 0); + return; + } + // the ' + 1' ensures a null value too! + Attribute[] toadd = ArrayCopy.copyOf(extra, extra.length + illegal.length + 1); + System.arraycopy(illegal, 0, toadd, extra.length, illegal.length); + + // right, we have legal content in 'content', and then in 'illegal' we + // have some legal content, and then some illegal content. + + // populate the list. + List list = buildEmptyList(); + assertTrue(list.addAll(0, Arrays.asList(content))); + quickCheck(list, content); + + // OK, we have a list of attributes.... behind the scenes, we have an + // an Element too... we need the element to get the setAttributes() + // method which in turn accesses the clearAndSet(). + Element myelement = list.get(0).getParent(); + assertNotNull(myelement); + + // check that the first to-add can be added. + list.add(0, toadd[0]); + //then remove it again. + assertTrue(toadd[0] == list.remove(0)); + + quickCheck(list, content); + + // now, add the illegal, and then inspect the list... + try { + myelement.setAttributes(Arrays.asList(toadd)); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + + // make sure that the member that previously could be added can + // still be added. + list.add(0, toadd[0]); + //then remove it again. + assertTrue(toadd[0] == list.remove(0)); + + // make sure it's all OK. + exercise(list, content); + + if (content.length < 2) { + //Assume.assumeTrue(content.length >= 2); + return; + } + + // now check to make sure that concurrency is not affected.... + Iterator it = list.iterator(); + // move it along at least once..... + assertTrue(content[0] == it.next()); + // now do a failed addAll. + try { + myelement.setAttributes(Arrays.asList(toadd)); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + // we should be able to move the iterator because the modCount should + // not have been affected..... + assertTrue(content[1] == it.next()); + } + + @Test + public void testAlreadyHasParent () { + // create an attribute. + final Attribute att = new Attribute("att", "val"); + // give this attribute a parent. + final Element parent = new Element("parent"); + parent.setAttribute(att); + + // none will have no attributes. + final Element none = new Element("none"); + // same will have an attribute with the same name as att. + final Element same = new Element("same"); + same.setAttribute("att", "val"); + // other will have an attrubute not the same as att. + final Element other = new Element("other"); + other.setAttribute("diff", "other"); + + try { + // cannot set an attribute with an existing parent. + none.setAttribute(att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot set an attribute with an existing parent. + same.setAttribute(att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot set an attribute with an existing parent. + other.setAttribute(att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + none.getAttributes().add(att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + same.getAttributes().add(att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + other.getAttributes().add(att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + none.getAttributes().add(0, att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + same.getAttributes().add(0, att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + other.getAttributes().add(0, att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot set an empty value. + // cannot add an attribute with an existing parent. + none.getAttributes().set(0, att); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + same.getAttributes().set(0, att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + try { + // cannot add an attribute with an existing parent. + other.getAttributes().set(0, att); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + + assertEquals("val", same.getAttributeValue("att")); + assertEquals("other", other.getAttributeValue("diff")); + assertFalse(none.hasAttributes()); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestBridgeMethods.java b/test/src/java/org/jdom/test/cases/TestBridgeMethods.java new file mode 100644 index 0000000..6dd2b80 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestBridgeMethods.java @@ -0,0 +1,88 @@ +package org.jdom.test.cases; + +import static org.jdom.test.util.UnitTestUtil.compare; +import static org.junit.Assert.assertTrue; + +import java.lang.reflect.Method; +import java.util.ArrayList; + +import org.junit.Test; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.EntityRef; +import org.jdom.NamespaceAware; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; + +/** + * When you use Generics Java automatically creates 'bridge' methods. + *

+ * Additionally, when you use the concept of 'co-variant return values' you + * create 'bridge' methods. By way of example, because we change the return + * type of clone() from Object to 'ZZZZ', Java is forced to put in a + * 'bridge' method that has an Object return type, even though we never + * actually call it. + *

+ * This has an impact on the code coverage tool Cobertura, which reports + * that there is missed code (and there is, the bridge method). It reports + * it as being '0' calls to the 'class' line (the class line is marked red). + *

+ * This test class exercises many of these 'bridge' methods, and thus improves + * the code coverage. + * + * @author Rolf Lear + * + */ +public class TestBridgeMethods { + + private static final Object[] invokeAll(final Object o, final String name) { + final ArrayList al = new ArrayList(); + for (Method m : o.getClass().getMethods()) { + if (m.getName().equals(name) && m.isBridge() && + m.getParameterTypes().length == 0) { + try { + al.add(m.invoke(o)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + } + return al.toArray(); + } + + /** + * Test various known Bridge Methods. This improves code coverage. + */ + @Test + public void testDetachBridges() { + final NamespaceAware[] cnt = { + new Text("Text"), + new CDATA("cdata"), + new Comment("comment"), + new DocType("root"), + new EntityRef("ref"), + new ProcessingInstruction("pi"), + new Attribute("att", "val"), + new Document() + }; + for (NamespaceAware c : cnt) { + Object[] a = invokeAll(c, "clone"); + for (Object o : a) { + compare(c, (NamespaceAware)o); + } + Object[] d = invokeAll(c, "detach"); + for (Object o : d) { + assertTrue(o == c); + } + Object[] e = invokeAll(c, "getParent"); + for (Object o : e) { + assertTrue(o == null); + } + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestCDATA.java b/test/src/java/org/jdom/test/cases/TestCDATA.java new file mode 100644 index 0000000..b469fce --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestCDATA.java @@ -0,0 +1,654 @@ +/*-- + + Copyright (C) 2006 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ +package org.jdom.test.cases; + +import org.jdom.CDATA; +import org.jdom.Content; +import org.jdom.Element; +import org.jdom.IllegalDataException; +import org.jdom.Text; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import static org.junit.Assert.*; + +/** + * Test for {@link CDATA}. + * + * @author Victor Toni + * @version 1.0.0 + */ +@SuppressWarnings("javadoc") +public final class TestCDATA { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (final String args[]) { + JUnitCore.runClasses(TestCDATA.class); + } + + /** + * Test the protected CDATA constructor. + */ + @Test + public void test_TCC() { + new CDATA() { + // check protected constructor via anonymous class + private static final long serialVersionUID = 200L; + }; + } + + /** + * Test the CDATA constructor with a valid and an invalid string. + */ + @Test + public void test_TCC___String() { + final String text = "this is a CDATA section"; + + final CDATA cdata = new CDATA(text); + + assertEquals( + "incorrect CDATA constructed", + "[CDATA: " + text + ']', + cdata.toString()); + + try { + new CDATA(""); + } catch (final IllegalDataException e) { + fail("CDATA constructor did throw exception on empty string"); + } + + try { + new CDATA(null); + } catch (final IllegalDataException e) { + fail("CDATA constructor did throw exception on null"); + } + + try { + new CDATA("some valid with a CDATA section ending ]]>"); + fail("CDATA constructor didn't catch invalid comment string"); + } catch (final IllegalDataException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Verify a simple object == object test + */ + @Test + public void test_TCM__boolean_equals_Object() { + final String text = "this is a CDATA section"; + final CDATA cdata = new CDATA(text); + + final Object object = cdata; + + assertTrue("object not equal to CDATA", cdata.equals(object)); + assertTrue("CDATA not equal to object", object.equals(cdata)); + } + + /** + * Test that a real hashcode is returned and that a different one is returned + * for a different CDATA. + */ + @Test + public void test_TCM__int_hashCode() { + final String text = "this is a CDATA section"; + final CDATA cdata = new CDATA(text); + + //only an exception would be a problem + int hash = -1; + try { + hash = cdata.hashCode(); + } + catch(final Exception exception) { + fail("bad hashCode"); + } + + // same text but created out of parts to avoid object re-usage + final CDATA similarCDATA = new CDATA("this"+ " is" +" a" + " CDATA" + " section"); + + //different CDATA sections, same text + final int similarHash = similarCDATA.hashCode(); + assertTrue("Different comments with same value have same hashcode", hash != similarHash); + + final CDATA otherCDATA = new CDATA("this is another CDATA section"); + + //only an exception would be a problem + int otherHash = otherCDATA.hashCode(); + assertTrue("Different comments have same hashcode", otherHash != hash); + assertTrue("Different comments have same hashcode", otherHash != similarHash); + } + + /** + * Test setting and resetting the text value of this CDATA. + */ + @Test + public void test_TCM__orgJdomText_setText_String() { + // simple text in CDATA section + final String text = "this is a CDATA section"; + final CDATA cdata = new CDATA(text); + assertEquals("incorrect CDATA text", text, cdata.getText()); + + // set it to the empty string + final String emptyString = ""; + cdata.setText(emptyString); + assertEquals("incorrect CDATA text", emptyString, cdata.getText()); + + // set it to a another string + final String otherString = "12345qwerty"; + cdata.setText(otherString); + assertEquals("incorrect CDATA text", otherString, cdata.getText()); + + // set it to the null (after it was set to another string so that we + // are sure something has changed) + cdata.setText(null); + assertEquals("incorrect CDATA text", emptyString, cdata.getText()); + + // the following test check for invalid data and transactional behavior + // means the content must not be change on exceptions so we set a default + + // set text with some special characters + final String specialText = "this is CDATA section with special characters as < > & & < > [[ ]] > with an invlaid CDATA section ending ]]>"; + + cdata.setText(invalidCDATAString); + fail("Comment setText didn't catch invalid CDATA string"); + } catch (final IllegalDataException exception) { + assertEquals("incorrect CDATA text after exception", text, cdata.getText()); + } + } + + /** + * Test appending text values to this CDATA. + */ + @Test + public void test_TCM___append_String() { + final String emptyString = ""; + final String nullString = null; + final String text = "this is a CDATA section"; + final String otherString = "12345qwerty"; + final String specialCharactersText = "this is CDATA section with special characters as < > & & < > [[ ]] > this is a CDATA section with special characters as ]]"; + final String cdataEndText = "this is aCDATA section with a CDATA end marke ]]> somewhere inside"; + + { + // simple text in CDATA section + final CDATA cdata = new CDATA(text); + assertEquals("incorrect CDATA text", text, cdata.getText()); + cdata.append(text); + assertEquals("incorrect CDATA text", text+text, cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the empty string + final CDATA cdata = new CDATA(emptyString); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append(emptyString); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append(text); + assertEquals("incorrect CDATA text", text, cdata.getText()); + cdata.append(nullString); + assertEquals("incorrect CDATA text", text, cdata.getText()); + cdata.append(specialCharactersText); + assertEquals("incorrect CDATA text", text + specialCharactersText, cdata.getText()); + cdata.append(nullString); + assertEquals("incorrect CDATA text", text + specialCharactersText, cdata.getText()); + cdata.append(emptyString); + assertEquals("incorrect CDATA text", text + specialCharactersText, cdata.getText()); + cdata.append(otherString); + assertEquals("incorrect CDATA text", text + specialCharactersText + otherString, cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to a another string + final CDATA cdata = new CDATA(otherString); + assertEquals("incorrect CDATA text", otherString, cdata.getText()); + cdata.append(text); + assertEquals("incorrect CDATA text", otherString + text, cdata.getText()); + cdata.append(nullString); + assertEquals("incorrect CDATA text", otherString + text, cdata.getText()); + cdata.append(specialCharactersText); + assertEquals("incorrect CDATA text", otherString + text + specialCharactersText, cdata.getText()); + cdata.append(nullString); + assertEquals("incorrect CDATA text", otherString + text + specialCharactersText, cdata.getText()); + cdata.append(emptyString); + assertEquals("incorrect CDATA text", otherString + text + specialCharactersText, cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the null (after it was set to another string so that we + // are sure something has changed) + final CDATA cdata = new CDATA(nullString); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append(specialCharactersText); + assertEquals("incorrect CDATA text", specialCharactersText, cdata.getText()); + cdata.append(nullString); + assertEquals("incorrect CDATA text", specialCharactersText, cdata.getText()); + cdata.append(text); + assertEquals("incorrect CDATA text", specialCharactersText + text, cdata.getText()); + cdata.append(nullString); + assertEquals("incorrect CDATA text", specialCharactersText + text, cdata.getText()); + cdata.append(emptyString); + assertEquals("incorrect CDATA text", specialCharactersText + text, cdata.getText()); + cdata.append(otherString); + assertEquals("incorrect CDATA text", specialCharactersText + text + otherString, cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the null (after it was set to another string so that we + // are sure something has changed) + final CDATA cdata = new CDATA(null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append((String) null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the null (after it was set to another string so that we + // are sure something has changed) + final CDATA cdata = new CDATA(null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append((Text) null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set text with some special characters + final CDATA cdata = new CDATA(specialCharactersText); + assertEquals("incorrect CDATA text", specialCharactersText, cdata.getText()); + cdata.append(specialCharactersText); + assertEquals("incorrect CDATA text", specialCharactersText + specialCharactersText, cdata.getText()); + try { + cdata.append(cdataEndText); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + + try { + // set text with some special characters which should result into an exception + final CDATA cdata = new CDATA(specialText); + assertEquals("incorrect CDATA text", specialText, cdata.getText()); + cdata.append(specialText); + + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test appending text values to this CDATA. + */ + @Test + public void test_TCM___append_Text() { + final String emptyString = ""; + final String nullString = null; + final String text = "this is a CDATA section"; + final String otherString = "12345qwerty"; + final String specialCharactersText = "this is CDATA section with special characters as < > & & < > [[ ]] > this is a CDATA section with special characters as ]]"; + final String cdataEndText = "this is aCDATA section with a CDATA end marke ]]> somewhere inside"; + + { + // simple text in CDATA section + final CDATA cdata = new CDATA(text); + assertEquals("incorrect CDATA text", text, cdata.getText()); + cdata.append(new Text(text)); + assertEquals("incorrect CDATA text", text+text, cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the empty string + final CDATA cdata = new CDATA(emptyString); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append(new Text(emptyString)); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append(new Text(text)); + assertEquals("incorrect CDATA text", text, cdata.getText()); + cdata.append(new Text(nullString)); + assertEquals("incorrect CDATA text", text, cdata.getText()); + cdata.append(new Text(specialCharactersText)); + assertEquals("incorrect CDATA text", text + specialCharactersText, cdata.getText()); + cdata.append(new Text(nullString)); + assertEquals("incorrect CDATA text", text + specialCharactersText, cdata.getText()); + cdata.append(new Text(emptyString)); + assertEquals("incorrect CDATA text", text + specialCharactersText, cdata.getText()); + cdata.append(new Text(otherString)); + assertEquals("incorrect CDATA text", text + specialCharactersText + otherString, cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to a another string + final CDATA cdata = new CDATA(otherString); + assertEquals("incorrect CDATA text", otherString, cdata.getText()); + cdata.append(new Text(text)); + assertEquals("incorrect CDATA text", otherString + text, cdata.getText()); + cdata.append(new Text(nullString)); + assertEquals("incorrect CDATA text", otherString + text, cdata.getText()); + cdata.append(new Text(specialCharactersText)); + assertEquals("incorrect CDATA text", otherString + text + specialCharactersText, cdata.getText()); + cdata.append(new Text(nullString)); + assertEquals("incorrect CDATA text", otherString + text + specialCharactersText, cdata.getText()); + cdata.append(new Text(emptyString)); + assertEquals("incorrect CDATA text", otherString + text + specialCharactersText, cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the null (after it was set to another string so that we + // are sure something has changed) + final CDATA cdata = new CDATA(nullString); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append(new Text(specialCharactersText)); + assertEquals("incorrect CDATA text", specialCharactersText, cdata.getText()); + cdata.append(new Text(nullString)); + assertEquals("incorrect CDATA text", specialCharactersText, cdata.getText()); + cdata.append(new Text(text)); + assertEquals("incorrect CDATA text", specialCharactersText + text, cdata.getText()); + cdata.append(new Text(nullString)); + assertEquals("incorrect CDATA text", specialCharactersText + text, cdata.getText()); + cdata.append(new Text(emptyString)); + assertEquals("incorrect CDATA text", specialCharactersText + text, cdata.getText()); + cdata.append(new Text(otherString)); + assertEquals("incorrect CDATA text", specialCharactersText + text + otherString, cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the null (after it was set to another string so that we + // are sure comething has changed) + final CDATA cdata = new CDATA(null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append((String) null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set it to the null (after it was set to another string so that we + // are sure something has changed) + final CDATA cdata = new CDATA(null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + cdata.append((Text) null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + { + // set text with some special characters + final CDATA cdata = new CDATA(specialCharactersText); + assertEquals("incorrect CDATA text", specialCharactersText, cdata.getText()); + cdata.append(new Text(specialCharactersText)); + assertEquals("incorrect CDATA text", specialCharactersText + specialCharactersText, cdata.getText()); + try { + cdata.append(new Text(cdataEndText)); + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + + try { + // set text with some special characters which sould result into an exception + final CDATA cdata = new CDATA(specialText); + assertEquals("incorrect CDATA text", specialText, cdata.getText()); + cdata.append(new Text(specialText)); + + fail("failed to detect CDATA end marker"); + } catch (final IllegalDataException exception) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Verify that the text of the CDATA matches expected value. + * It assumes that the constructor is working correctly + */ + @Test + public void test_TCM__String_getText() { + { + final String text = "this is a CDATA section"; + + final CDATA cdata = new CDATA(text); + assertEquals("incorrect CDATA text", text, cdata.getText()); + } + + { + //set it to the empty string + final String emptyString = ""; + final CDATA cdata = new CDATA(emptyString); + assertEquals("incorrect CDATA text", emptyString, cdata.getText()); + } + + { + // set it to a another string + final String otherString = "12345qwerty"; + final CDATA cdata = new CDATA(otherString); + assertEquals("incorrect CDATA text", otherString, cdata.getText()); + } + + { + // set it to the null + final CDATA cdata = new CDATA(null); + assertEquals("incorrect CDATA text", "", cdata.getText()); + } + } + + /** + * check for the expected toString text value of Comment. + */ + @Test + public void test_TCM__String_toString() { + { + final String text = "this is a simple CDATA section"; + final CDATA cdata = new CDATA(text); + + assertEquals( + "incorrect CDATA constructed", + "[CDATA: " + text + ']', + cdata.toString()); + } + + { + final String text = "this is CDATA section with special characters as < > & & < > [[ ]] > and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + + +/** + * Please put a description of your test here. + * + * @author Philip Nelson + * @version 1.0 + */ +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Element; +import org.jdom.IllegalDataException; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +@SuppressWarnings("javadoc") +public final class TestComment { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestComment.class); + } + + @Test + public void test_TCC() { + // test creating a subclass with an anonymous instance + final Comment theComment = new Comment() { + // no modifications. + private static final long serialVersionUID = 200L; + }; + assertTrue(null == theComment.getText()); + } + /** + * Test the comment constructor with a valid and an invalid string. + */ + @Test + public void test_TCC___String() { + Comment theComment = new org.jdom.Comment("this is a comment"); + + assertEquals( + "incorrect Comment constructed", + "[Comment: ]", + theComment.toString()); + try { + theComment = new org.jdom.Comment(null); + fail("Comment constructor didn't catch invalid comment string"); + } catch (IllegalDataException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + /** + * Verify a simple object == object test + */ + @Test + public void test_TCM__boolean_equals_Object() { + Comment com = new Comment("test"); + + Object ob = com; + + assertTrue("object not equal to comment", com.equals(ob)); + } + /** + * Test that a real hashcode is returned and that a different one is returned + * for a different comment. + */ + @Test + public void test_TCM__int_hashCode() { + //not sure what to test! + + Comment com = new Comment("test"); + //only an exception would be a problem + int i = -1; + try { + i = com.hashCode(); + } + catch(Exception e) { + fail("bad hashCode"); + } + Comment com2 = new Comment("test"); + //different comments, same text + int x = com2.hashCode(); + assertTrue("Different comments with same value have same hashcode", x != i); + Comment com3 = new Comment("test2"); + //only an exception would be a problem + int y = com3.hashCode(); + assertTrue("Different comments have same hashcode", y != x); + } + + /** + * Test setting and resetting the text value of this Comment. + */ + @Test + public void test_TCM__OrgJdomComment_setText_String() { + Comment theComment= new org.jdom.Comment("this is a comment"); + + assertEquals( + "incorrect Comment constructed", + "[Comment: ]", + theComment.toString()); + + //set it to the empty string + theComment.setText(""); + + assertEquals("incorrect Comment text", "", theComment.getText()); + assertEquals(theComment.getText(), theComment.getValue()); + //set it to a new string + theComment.setText("12345qwerty"); + + assertEquals("incorrect Comment text", "12345qwerty", theComment.getText()); + assertEquals(theComment.getText(), theComment.getValue()); + + //tests for invalid data but setText doesn't + + try { + theComment.setText(null); + fail("Comment setText didn't catch invalid comment string"); + } catch (IllegalDataException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + char c= 0x11; + StringBuilder b= new StringBuilder("hhhh"); + b.setCharAt(2, c); + theComment.setText(b.toString()); + fail("Comment setText didn't catch invalid comment string"); + } catch (IllegalDataException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + } + + /** + * Match the XML fragment this comment produces. + */ + @Test + public void test_TCM__String_getSerializedForm() { + + /** No op because the method is deprecated + Comment theComment = new org.jdom.Comment("this is a comment"); + + assertEquals( + "incorrect Comment constructed", + "", + theComment.getSerializedForm()); + */ + + } + + /** + * verify that the text of the Comment matches expected value. + */ + @Test + public void test_TCM__String_getText() { + Comment theComment = new org.jdom.Comment("this is a comment"); + + assertEquals( + "incorrect Comment constructed", + "this is a comment", + theComment.getText()); + + + } + /** + * check for the expected toString text value of Comment. + */ + @Test + public void test_TCM__String_toString() { + Comment theComment= new org.jdom.Comment("this is a comment"); + + assertEquals( + "incorrect Comment constructed", + "[Comment: ]", + theComment.toString()); + try { + theComment= new org.jdom.Comment(null); + fail("Comment constructor didn't catch invalid comment string"); + } catch (IllegalDataException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void testCloneDetatchParentComment() { + Element parent = new Element("root"); + Comment content = new Comment("val"); + parent.addContent(content); + Comment clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + + @Test + public void testContentCType() { + assertTrue(Content.CType.Comment == new Comment("").getCType()); + } +} diff --git a/test/src/java/org/jdom/test/cases/TestContentList.java b/test/src/java/org/jdom/test/cases/TestContentList.java new file mode 100644 index 0000000..180915b --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestContentList.java @@ -0,0 +1,150 @@ +package org.jdom.test.cases; + +import static org.jdom.test.util.UnitTestUtil.checkException; +import static org.jdom.test.util.UnitTestUtil.failNoException; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Element; +import org.jdom.internal.ArrayCopy; +import org.jdom.test.util.AbstractTestList; + +import org.junit.Before; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestContentList extends AbstractTestList { + + public TestContentList() { + super(Content.class, false); + } + + @Override + public List buildEmptyList() { + Element e = new Element("dummy"); + return e.getContent(); + } + + @Override + public Content[] buildSampleContent() { + return new Content[]{ new Element("zero"), + new Element("one"), new Element("two"), + new Element("three"), new Element("four"), + new Element("five"), new Element("six")}; + } + + @Override + public Content[] buildAdditionalContent() { + return new Content[]{ new Element("seven"), + new Element("eight")}; + } + + @Override + public Object[] buildIllegalClassContent() { + Object[] ret = new Object[] { + new Integer(10), + new StringBuilder("Hello!") + }; + return ret; + } + + @Override + public Content[] buildIllegalArgumentContent() { + return new Content[]{new DocType("root")}; + } + + @Before + public void detatchAll () { + // make sure all content is detatched before each test. + for (Content c : buildSampleContent()) { + c.detach(); + } + } + + @Test + public void testIllegalSetContent() { + final Content[] illegal = buildIllegalArgumentContent(); + if (illegal.length <= 0) { + //Assume.assumeTrue(illegal.length > 0); + return; + } + final Content[] extra = buildAdditionalContent(); + if (extra.length <= 0) { + //Assume.assumeTrue(extra.length > 0); + return; + } + final Content[] content = buildSampleContent(); + if (content.length <= 0) { + // Assume.assumeTrue(content.length > 0); + return; + } + // the ' + 1' ensures a null value too! + Content[] toadd = ArrayCopy.copyOf(extra, extra.length + illegal.length + 1); + System.arraycopy(illegal, 0, toadd, extra.length, illegal.length); + + // right, we have legal content in 'content', and then in 'illegal' we + // have some legal content, and then some illegal content. + + // populate the list. + List list = buildEmptyList(); + assertTrue(list.addAll(0, Arrays.asList(content))); + quickCheck(list, content); + + // OK, we have a list of attributes.... behind the scenes, we have an + // an Element too... we need the element to get the setContents() + // method which in turn accesses the clearAndSet(). + Element myelement = list.get(0).getParentElement(); + assertNotNull(myelement); + + // check that the first to-add can be added. + list.add(0, toadd[0]); + //then remove it again. + assertTrue(toadd[0] == list.remove(0)); + + quickCheck(list, content); + + // now, add the illegal, and then inspect the list... + try { + myelement.setContent(Arrays.asList(toadd)); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + + // make sure that the member that previously could be added can + // still be added. + list.add(0, toadd[0]); + //then remove it again. + assertTrue(toadd[0] == list.remove(0)); + + // make sure it's all OK. + exercise(list, content); + + if (content.length < 2) { + //Assume.assumeTrue(content.length >= 2); + return; + } + + // now check to make sure that concurrency is not affected.... + Iterator it = list.iterator(); + // move it along at least once..... + assertTrue(content[0] == it.next()); + // now do a failed addAll. + try { + myelement.setContent(Arrays.asList(toadd)); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + // we should be able to move the iterator because the modCount should + // not have been affected..... + assertTrue(content[1] == it.next()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestDefaultJDOMFactory.java b/test/src/java/org/jdom/test/cases/TestDefaultJDOMFactory.java new file mode 100644 index 0000000..1f9322b --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestDefaultJDOMFactory.java @@ -0,0 +1,21 @@ +package org.jdom.test.cases; + +import org.jdom.DefaultJDOMFactory; +import org.jdom.JDOMFactory; + +@SuppressWarnings("javadoc") +public class TestDefaultJDOMFactory extends AbstractTestJDOMFactory { + + /** + * @param located + */ + public TestDefaultJDOMFactory() { + super(false); + } + + @Override + protected JDOMFactory buildFactory() { + return new DefaultJDOMFactory(); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestDescendantFilterIterator.java b/test/src/java/org/jdom/test/cases/TestDescendantFilterIterator.java new file mode 100644 index 0000000..59e6b03 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestDescendantFilterIterator.java @@ -0,0 +1,159 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.filter.ElementFilter; +import org.jdom.util.IteratorIterable; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestDescendantFilterIterator { + private static final String[] fellowship = new String[] { + "frodo", "sam", "pippin", "merry", + "legolas", "aragorn", "gimli", "boromir", "gandalf" + }; + + private static final IteratorIterable buildIterator() { + Element root = new Element("root"); + Document doc = new Document(root); + for (String c : fellowship) { + root.addContent(new Element(c)); + } + + return doc.getDescendants(new ElementFilter()); + } + + @Test + public void testIteration() { + Iterator it = buildIterator(); + assertTrue(it.hasNext()); + Object f = it.next(); + assertNotNull(f != null); + assertTrue(f instanceof Element); + assertEquals("root", ((Element)f).getName()); + for (int i = 0; i < fellowship.length; i++) { + assertTrue(it.hasNext()); + assertEquals(fellowship[i], it.next().getName()); + } + assertFalse(it.hasNext()); + try { + assertTrue(null != it.next().toString()); + fail("Should not be able to iterate off the end of the descendants."); + } catch (NoSuchElementException nse) { + // good + } catch (Exception e) { + fail("Expected NoSuchElementException, but got " + e.getClass().getName()); + } + + } + + @Test + public void testIterable() { + int i = 0; + for (Content c : buildIterator()) { + assertNotNull(c != null); + assertTrue(c instanceof Element); + Element e = (Element)c; + if (i == 0) { + assertEquals("root", e.getName()); + } else { + assertEquals(fellowship[i - 1], e.getName()); + } + i++; + } + } + + @Test + public void testRemoveOne() { + Iterator it = buildIterator(); + assertTrue(it.hasNext()); + try { + it.remove(); + fail("Should not be able to remove before next()."); + } catch (IllegalStateException ise) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail ("Expected IllegalStateException but got " + e.getClass()); + } + + Object f = it.next(); + assertNotNull(f != null); + assertTrue(f instanceof Element); + assertEquals("root", ((Element)f).getName()); + + // this should remove the root element, which effectively should + // make the descendant iterator empty. + it.remove(); + + try { + it.remove(); + fail("Should not be able to double-remove."); + } catch (IllegalStateException ise) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail ("Expected IllegalStateException but got " + e.getClass()); + } + + + assertFalse(it.hasNext()); + try { + assertTrue(null != it.next().toString()); + fail("Should not be able to iterate off the end of the descendants."); + } catch (NoSuchElementException nse) { + // good + } catch (Exception e) { + fail("Expected NoSuchElementException, but got " + e.getClass().getName()); + } + + } + + @Test + public void testIllegal() { + try { + Element emt = new Element("root"); + emt.getDescendants(null); + } catch (NullPointerException npe) { + // good + } catch (Exception e) { + fail("Expected NullPointerException, but got " + e.getClass().getName()); + } + } + + @Test + public void testDeep() { + Document doc = new Document(); + Element emt = new Element("root"); + Element kid = new Element("kid"); + Element leaf = new Element("leaf"); + kid.addContent(leaf); + emt.addContent(kid); + emt.addContent(new Element("sib")); + doc.addContent(emt); + String[] tags = new String[] {"root", "kid", "leaf", "sib"}; + ArrayList al = new ArrayList(); + Iterator it = doc.getDescendants(new ElementFilter()); + while (it.hasNext()) { + al.add(it.next().getName()); + } + assertTrue(al.size() == tags.length); + for (int i = 0; i < tags.length; i++) { + assertEquals(tags[i], al.get(i)); + } + + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestDescendantIterator.java b/test/src/java/org/jdom/test/cases/TestDescendantIterator.java new file mode 100644 index 0000000..d1184e2 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestDescendantIterator.java @@ -0,0 +1,234 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import org.junit.Test; + +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.test.util.UnitTestUtil; +import org.jdom.util.IteratorIterable; + +@SuppressWarnings("javadoc") +public class TestDescendantIterator { + private static final String[] fellowship = new String[] { + "frodo", "sam", "pippin", "merry", + "legolas", "aragorn", "gimli", "boromir", "gandalf" + }; + + private static final IteratorIterable buildIterator() { + Element root = new Element("root"); + Document doc = new Document(root); + for (String c : fellowship) { + root.addContent(new Element(c)); + } + + return doc.getDescendants(); + } + + private static final Element buildTestDoc() { + Element hobbits = new Element(fellowship[0]); + hobbits.addContent(new Element(fellowship[1])); + hobbits.addContent(new Element(fellowship[2])); + hobbits.addContent(new Element(fellowship[3])); + Element humans = new Element(fellowship[4]); + humans.addContent(new Element(fellowship[5])); + humans.addContent(new Element(fellowship[6])); + humans.addContent(new Element(fellowship[7])); + hobbits.addContent(humans); + // gandalf + hobbits.addContent(new Element(fellowship[8])); + return new Element("root").addContent(hobbits); + } + + @Test + public void testIteration() { + Iterator it = buildIterator(); + assertTrue(it.hasNext()); + Object f = it.next(); + assertNotNull(f != null); + assertTrue(f instanceof Element); + assertEquals("root", ((Element)f).getName()); + for (int i = 0; i < fellowship.length; i++) { + assertTrue(it.hasNext()); + assertEquals(fellowship[i], ((Element)it.next()).getName()); + } + assertFalse(it.hasNext()); + try { + assertTrue(null != it.next().toString()); + fail("Should not be able to iterate off the end of the descendants."); + } catch (NoSuchElementException nse) { + // good + } catch (Exception e) { + fail("Expected NoSuchElementException, but got " + e.getClass().getName()); + } + + } + + @Test + public void testIterable() { + int i = 0; + for (Content c : buildIterator()) { + assertNotNull(c != null); + assertTrue(c instanceof Element); + Element e = (Element)c; + if (i == 0) { + assertEquals("root", e.getName()); + } else { + assertEquals(fellowship[i - 1], e.getName()); + } + i++; + } + } + + @Test + public void testRemoveOne() { + Iterator it = buildIterator(); + assertTrue(it.hasNext()); + Object f = it.next(); + assertNotNull(f != null); + assertTrue(f instanceof Element); + assertEquals("root", ((Element)f).getName()); + + // this should remove the root element, which effectively should + // make the descendant iterator empty. + it.remove(); + + + assertFalse(it.hasNext()); + try { + assertTrue(null != it.next().toString()); + fail("Should not be able to iterate off the end of the descendants."); + } catch (NoSuchElementException nse) { + // good + } catch (Exception e) { + fail("Expected NoSuchElementException, but got " + e.getClass().getName()); + } + + } + + + @Test + public void testRemoves() { + for (int i = fellowship.length - 1; i >= 0; i--) { + checkRemove(i); + } + } + + private void checkIterator(final Iterator it, final String...values) { + for (int i = 0; i < values.length; i++) { + assertTrue(it.hasNext()); + assertEquals(values[i], ((Element)it.next()).getName()); + } + assertFalse(it.hasNext()); + try { + assertTrue(null != it.next().toString()); + fail("Should not be able to iterate off the end of the descendants."); + } catch (NoSuchElementException nse) { + // good + } catch (Exception e) { + fail("Expected NoSuchElementException, but got " + e.getClass().getName()); + } + } + + private void checkRemove(final int remove) { + Element doc = buildTestDoc(); + checkIterator(doc.getDescendants(), fellowship); + Iterator it1 = doc.getDescendants(); + it1.next(); + for (int i = 0; i < remove; i++) { + it1.next(); + } + it1.remove(); + try { + it1.remove(); + fail ("Should not be able to double-remove"); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalStateException.class, e); + } + ArrayList al = new ArrayList(); + Iterator it2 = doc.getDescendants(); + int i = remove; + while (--i >= 0) { + it2.next(); + } + while (it2.hasNext()) { + al.add(((Element)it2.next()).getName()); + } + checkIterator(it1, al.toArray(new String[al.size()])); + } + + @Test + public void testDeepNesting() { + ArrayList names = new ArrayList(64); + Element p = new Element("name"); + names.add("name"); + Document doc = new Document(p); + for (int i = 0; i < 64; i++) { + names.add("name" + i); + final Element e = new Element("name" + i); + p.getContent().add(e); + p = e; + } + + checkIterator(doc.getDescendants(), names.toArray(new String[names.size()])); + } + + @Test + public void testSpecialCaseRemove() { + // this is designed to test a special case: + // the iterator's next move was to go down next, but, we did a remove(), + // and now we can't go down next, but, there's no more siblings either, + // so our next move will be up, but the level up is no more siblings + // either, but some level above that has siblings, so we go there.... + /* + * + * + * + * + * + * + * + * + */ + Element root = new Element("root"); + Element filler1 = new Element("filler"); + root.addContent(filler1); + + // right, this will be the thing we remove. + Element toremove = new Element("toremove"); + filler1.addContent(toremove); + + // but, this needs to have kids to go down to... + toremove.addContent(new Element("child")); + + + // this will be what next() returns after the remove. + Element postremove = new Element("postremove"); + root.addContent(postremove); + + Document doc = new Document(root); + Iterator it = doc.getDescendants(); + while (it.hasNext()) { + Content c = it.next(); + if (c == toremove) { + it.remove(); + assertTrue(it.hasNext()); + assertTrue(postremove == it.next()); + } + } + + } + + + +} diff --git a/test/src/java/org/jdom/test/cases/TestDocType.java b/test/src/java/org/jdom/test/cases/TestDocType.java new file mode 100644 index 0000000..8dd2a50 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestDocType.java @@ -0,0 +1,335 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import org.jdom.*; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import static org.junit.Assert.*; + +@SuppressWarnings("javadoc") +public final class TestDocType { + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestDocType.class); + } + + + /** + * Test a simple DocType with a name. + */ + @Test + public void test_TCC() { + DocType theDocType = new DocType() { + // change nothing + private static final long serialVersionUID = 200L; + }; + + assertNull("incorrect element name", theDocType.getElementName()); + } + + /** + * Test a simple DocType with a name. + */ + @Test + public void test_TCC___String() { + DocType theDocType = new DocType("anElement"); + + assertEquals("incorrect element name", "anElement", theDocType.getElementName()); + } + + /** + * test both the setting of the element name and systemID. + */ + @Test + public void test_TCC___String_String() { + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", systemID); + + assertEquals("incorrect element name", "anElement", theDocType.getElementName()); + assertEquals("incorrect system ID", systemID, theDocType.getSystemID()); + } + + /** + * test with element name, public and systemIDs. + */ + @Test + public void test_TCC___String_String_String() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", publicID, systemID); + + assertEquals("incorrect element name", "anElement", theDocType.getElementName()); + assertEquals("incorrect public ID", publicID, theDocType.getPublicID()); + assertEquals("incorrect system ID", systemID, theDocType.getSystemID()); + + } + + /** + * Do an object comparison with itself to confirm boolean equals works. + */ + @Test + public void test_TCM__boolean_equals_Object() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", publicID, systemID); + + Object ob = theDocType; + assertEquals(theDocType, ob); + } + + /** + * look for a integer hashCode + */ + @Test + public void test_TCM__int_hashCode() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", publicID, systemID); + assertTrue(theDocType.hashCode() == theDocType.hashCode()); + // assuming no exception was thrown, an integer was created. + } + + /** + * use toString to test the clone. + */ + @Test + public void test_TCM__Object_clone() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", publicID, systemID); + + DocType theDocType2 = theDocType.clone(); + + //assuming toString works as advertised.... + + assertEquals(theDocType.toString(), theDocType2.toString()); + } + + /** + * Test the setter for publicID. + */ + @Test + public void test_TCM__OrgJdomDocType_setPublicID_String() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + + DocType theDocType = new DocType("anElement"); + + theDocType.setPublicID(publicID); + + assertEquals(publicID, theDocType.getPublicID()); + } + + /** + * Test the setter for SystemID + */ + @Test + public void test_TCM__OrgJdomDocType_setSystemID_String() { + String systemID = "FILE://temp/doodah.dtd"; + + DocType theDocType = new DocType("anElement"); + + theDocType.setSystemID(systemID); + + assertEquals(systemID, theDocType.getSystemID()); + } + + /** + * Test getElementName. + */ + @Test + public void test_TCM__String_getElementName() { + DocType theDocType = new DocType("anElement"); + + assertEquals("incorrect element name", "anElement", theDocType.getElementName()); + } + + /** + * Test that getPublicID matches the value from the constructor. + */ + @Test + public void test_TCM__String_getPublicID() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + + DocType theDocType = new DocType("anElement", publicID, ""); + + assertEquals(publicID, theDocType.getPublicID()); + assertTrue("".equals(theDocType.getValue())); + } + + /** + * Test that getSerializedForm works as expected. + */ + @Test + public void test_TCM__String_getSerializedForm() { + /** No op because the method is deprecated + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", publicID, systemID); + + String result = theDocType.getSerializedForm(); + String compareTo = + ""; + + assertEquals("incorrect serialized form", result, compareTo); + */ + } + + /** + * Test that getSystemID returns the same value as set in the constructor. + */ + @Test + public void test_TCM__String_getSystemID() { + String systemID = "FILE://temp/doodah.dtd"; + DocType theDocType = new DocType("anElement", systemID); + + assertEquals(systemID, theDocType.getSystemID()); + assertTrue("".equals(theDocType.getValue())); + } + + /** + * Test toString returns the expected string. + */ + @Test + public void test_TCM__String_toString() { + String publicID = "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"; + String systemID = "FILE://temp/test.dtd"; + DocType theDocType = new DocType("anElement", publicID, systemID); + String result = theDocType.toString(); + String compareTo = + "[DocType: ]"; + + assertEquals("incorrect toString form", compareTo, result); + assertTrue("".equals(theDocType.getValue())); + } + + @Test (expected=IllegalNameException.class) + public void testIllegalNameA() { + new DocType("f0$0"); + } + + @Test (expected=IllegalNameException.class) + public void testIllegalNameB() { + new DocType("f0$0", "systemid"); + } + + @Test (expected=IllegalNameException.class) + public void testIllegalNameC() { + new DocType("f0$0", "publicid", "systemid"); + } + + @Test (expected=IllegalNameException.class) + public void testIllegalNameD() { + DocType dt = new DocType("foo", "publicid", "systemid"); + dt.setElementName("f0$0"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalPublicA() { + new DocType("foo", "pub~id", null); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalPublicB() { + DocType dt = new DocType("foo", "pubid", null); + dt.setPublicID("pub~id"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalSystemA() { + char illegal = 0x0B; + new DocType("foo", "pubid", "sys" + illegal + "id"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalSystemB() { + DocType dt = new DocType("foo", "pubid", "sysid"); + char illegal = 0x0B; + dt.setSystemID("sys" + illegal + "id"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalSystemC() { + DocType dt = new DocType("foo", "pubid", "sysid"); + dt.setSystemID("sys'id \" with quote"); + } + + @Test + public void testCloneDetatchParentDocType() { + Document parent = new Document(); + DocType content = new DocType("val"); + parent.addContent(content); + DocType clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + + @Test + public void testContentCType() { + assertTrue(Content.CType.DocType == new DocType("root").getCType()); + } +} diff --git a/test/src/java/org/jdom/test/cases/TestDocument.java b/test/src/java/org/jdom/test/cases/TestDocument.java new file mode 100644 index 0000000..1200bce --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestDocument.java @@ -0,0 +1,1039 @@ +package org.jdom.test.cases; + +/* Please run replic.pl on me ! */ +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import org.jdom.*; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import static org.junit.Assert.*; + +import java.io.*; +import java.util.*; + +import org.jdom.filter.ContentFilter; +import org.jdom.filter.ElementFilter; +import org.jdom.output.*; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public final class TestDocument { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestDocument.class); + } + + /** + * Test constructor of Document with a List of content including the root element. + */ + @Test + public void test_TCC___List() { + Element bogus = new Element("bogus-root"); + Element element = new Element("element"); + Comment comment = new Comment("comment"); + List list = new ArrayList(); + + list.add(element); + list.add(comment); + Document doc = new Document(list); + // Get a live list back + list = doc.getContent(); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + + //no root element + element.detach(); + try { + doc.getRootElement(); + fail("didn't catch missing root element"); + } catch (IllegalStateException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //set root back, then try to add another element to our + //live list + doc.setRootElement(element); + try { + list.add(bogus); + fail("didn't catch duplicate root element"); + } catch (IllegalAddException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + assertEquals("incorrect root element returned", element, doc.getRootElement()); + + //how about replacing it in our live list + try { + Element oldRoot = doc.getRootElement(); + int i = doc.indexOf(oldRoot); + list.set(i, bogus); + } catch (Exception e) { + fail("Root replacement shouldn't have throw a exception"); + } + //and through the document + try { + doc.setRootElement(element); + } catch (Exception e) { + fail("Root replacement shouldn't have throw a exception"); + } + + list = null; + try { + doc = new Document(list); + } catch (IllegalAddException e) { + fail("didn't handle null list"); + } catch (NullPointerException e) { + fail("didn't handle null list"); + } + + } + + /** + * Test constructor of a Document with a List of content and a DocType. + */ + @Test + public void test_TCC___List_OrgJdomDocType() { + Element bogus = new Element("bogus-root"); + Element element = new Element("element"); + Comment comment = new Comment("comment"); + DocType docType = new DocType("element"); + List list = new ArrayList(); + + list.add(docType); + list.add(element); + list.add(comment); + Document doc = new Document(list); + // Get a live list back + list = doc.getContent(); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + + element.detach(); + try { + doc.getRootElement(); + fail("didn't catch missing root element"); + } catch (IllegalStateException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //set root back, then try to add another element to our + //live list + doc.setRootElement(element); + try { + list.add(bogus); + fail("didn't catch duplicate root element"); + } catch (IllegalAddException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + assertEquals("incorrect root element returned", element, doc.getRootElement()); + + //how about replacing it in our live list + try { + Element oldRoot = doc.getRootElement(); + int i = doc.indexOf(oldRoot); + list.set(i,bogus); + } catch (Exception e) { + fail("Root replacement shouldn't have throw a exception"); + } + //and through the document + try { + doc.setRootElement(element); + } catch (Exception e) { + fail("Root replacement shouldn't have throw a exception"); + } + + list = null; + try { + doc = new Document(list); + } catch (IllegalAddException e) { + fail("didn't handle null list"); + } catch (NullPointerException e) { + fail("didn't handle null list"); + } + + } + + /** + * Test the constructor with only a root element. + */ + @Test + public void test_TCC___OrgJdomElement() { + Element element = new Element("element"); + + Document doc = new Document(element); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + + element = null; + try { + doc = new Document(element); + } catch (IllegalAddException e) { + fail("didn't handle null element"); + } catch (NullPointerException e) { + fail("didn't handle null element"); + } + + } + + /** + * Test constructor of Document with and Element and Doctype + */ + @Test + public void test_TCC___OrgJdomElement_OrgJdomDocType() { + Element element = new Element("element"); + DocType docType = new DocType("element"); + + Document doc = new Document(element, docType); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + + docType = new DocType("element"); + element = null; + try { + doc = new Document(element, docType); + } catch (IllegalAddException e) { + fail("didn't handle null element"); + } catch (NullPointerException e) { + fail("didn't handle null element"); + } + + } + + /** + * Test constructor of Document with and Element and Doctype + */ + @Test + public void test_TCC___OrgJdomElement_OrgJdomDocType_JavaLangString() { + Element element = new Element("element"); + DocType docType = new DocType("element"); + String baseuri = "BaseURI"; + + Document doc = new Document(element, docType, baseuri); + assertTrue(doc.hasRootElement()); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + assertEquals("incorrect BaseURI returned", "BaseURI", doc.getBaseURI()); + + try { + element.detach(); + docType.detach(); + doc = new Document(null, docType, baseuri); + assertFalse(doc.hasRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + assertEquals("incorrect BaseURI returned", "BaseURI", doc.getBaseURI()); + try { + assertEquals("incorrect root element returned", null, doc.getRootElement()); + fail ("Should not be ableto query the root element if it is not set..."); + } catch (IllegalStateException ise) { + // OK, Root Element not set. + } + } catch (IllegalAddException e) { + fail("didn't handle null element"); + } catch (NullPointerException e) { + fail("didn't handle null element"); + } + + try { + element.detach(); + docType.detach(); + doc = new Document(element, null, baseuri); + assertTrue(doc.hasRootElement()); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", null, doc.getDocType()); + assertEquals("incorrect BaseURI returned", "BaseURI", doc.getBaseURI()); + } catch (IllegalAddException e) { + fail("didn't handle null docType"); + } catch (NullPointerException e) { + fail("didn't handle null docType"); + } + + try { + element.detach(); + docType.detach(); + doc = new Document(element, docType, null); + assertTrue(doc.hasRootElement()); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + assertEquals("incorrect BaseURI returned", null, doc.getBaseURI()); + } catch (IllegalAddException e) { + fail("didn't handle null baseuri"); + } catch (NullPointerException e) { + fail("didn't handle null baseuri"); + } + + try { + element.detach(); + docType.detach(); + doc = new Document(null, docType, null); + assertFalse(doc.hasRootElement()); + try { + assertEquals("incorrect root element returned", null, doc.getRootElement()); + fail ("Should not be ableto query the root element if it is not set..."); + } catch (IllegalStateException ise) { + // OK, Root Element not set. + } + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + assertEquals("incorrect BaseURI returned", null, doc.getBaseURI()); + } catch (IllegalAddException e) { + fail("didn't handle null element and baseuri"); + } catch (NullPointerException e) { + fail("didn't handle null element and baseuri"); + } + + try { + element.detach(); + docType.detach(); + doc = new Document(null, null, null); + assertFalse(doc.hasRootElement()); + try { + assertEquals("incorrect root element returned", null, doc.getRootElement()); + fail ("Should not be ableto query the root element if it is not set..."); + } catch (IllegalStateException ise) { + // OK, Root Element not set. + } + assertEquals("incorrect doc type returned", null, doc.getDocType()); + assertEquals("incorrect BaseURI returned", null, doc.getBaseURI()); + } catch (IllegalAddException e) { + fail("didn't handle null parameters"); + } catch (NullPointerException e) { + fail("didn't handle null parameters"); + } + + } + + /** + * Test object equality. + */ + @Test + public void test_TCM__boolean_equals_Object() { + Element element = new Element("element"); + + Object doc = new Document(element); + assertEquals("invalid object equality", doc, doc); + + } + + /** + * Test removeContent for a given Comment + */ + @Test + public void test_TCM__boolean_removeContent_OrgJdomComment() { + Element element = new Element("element"); + Comment comment = new Comment("comment"); + ArrayList list = new ArrayList(); + + list.add(element); + list.add(comment); + Document doc = new Document(list); + assertTrue("incorrect comment removed",! doc.removeContent(new Comment("hi"))); + assertTrue("didn't remove comment", doc.removeContent(comment)); + + assertTrue("comment not removed", doc.getContent().size() == 1); + } + + /** + * Test removeContent with the supplied ProcessingInstruction. + */ + @Test + public void test_TCM__boolean_removeContent_OrgJdomProcessingInstruction() { + Element element = new Element("element"); + ProcessingInstruction pi = new ProcessingInstruction("test", "comment"); + ArrayList list = new ArrayList(); + + list.add(element); + list.add(pi); + Document doc = new Document(list); + assertTrue("incorrect pi removed",! doc.removeContent(new ProcessingInstruction("hi", "there"))); + assertTrue("didn't remove pi", doc.removeContent(pi)); + + assertTrue("PI not removed", doc.getContent().size() == 1); + + } + + /** + * Test hashcode function. + */ + @Test + public void test_TCM__int_hashCode() { + Element element = new Element("test"); + Document doc = new Document(element); + + //only an exception would be a problem + int i = -1; + try { + i = doc.hashCode(); + } + catch(Exception e) { + fail("bad hashCode"); + } + + + Element element2 = new Element("test"); + Document doc2 = new Document(element2); + //different Documents, same text + int x = doc2.hashCode(); + assertTrue("Different Elements with same value have same hashcode", x != i); + + } + + /** + * Test code goes here. Replace this comment. + */ + @Test + public void test_TCM__Object_clone() { + //do the content tests to 2 levels deep to verify recursion + Element element = new Element("el"); + Namespace ns = Namespace.getNamespace("urn:hogwarts"); + element.setAttribute(new Attribute("name", "anElement")); + Element child1 = new Element("child", ns); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("firstChild", ns); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + child1.addContent(child2); + element.addContent(child1); + element.addContent(child3); + + //add mixed content to the nested child2 element + Comment comment = new Comment("hi"); + child2.addContent(comment); + CDATA cdata = new CDATA("gotcha"); + child2.addContent(cdata); + ProcessingInstruction pi = new ProcessingInstruction("tester", "do=something"); + child2.addContent(pi); + EntityRef entity = new EntityRef("wizards"); + child2.addContent(entity); + child2.addContent("finally a new wand!"); + + //a little more for the element + element.addContent("top level element text"); + Comment topComment = new Comment("some comment"); + + Document doc = new Document(element); + doc.addContent(topComment); + Document docClone = doc.clone(); + element = null; + child3 = null; + child2 = null; + child1 = null; + + List list = docClone.getRootElement().getContent(); + + //finally the test + assertEquals("wrong comment", ((Comment)docClone.getContent().get(1)).getText(), "some comment"); + assertEquals("wrong child element", ((Element)list.get(0)).getName(), "child" ); + assertEquals("wrong child element", ((Element)list.get(1)).getName(), "child" ); + Element deepClone = ((Element)list.get(0)).getChild("firstChild", Namespace.getNamespace("urn:hogwarts")); + + assertEquals("wrong nested element","firstChild", deepClone.getName()); + //comment + assertTrue("deep clone comment not a clone", deepClone.getContent().get(0) != comment); + comment = null; + assertEquals("incorrect deep clone comment", "hi", ((Comment)deepClone.getContent().get(0)).getText()); + //CDATA + + assertEquals("incorrect deep clone CDATA", "gotcha", ((CDATA)deepClone.getContent().get(1)).getText()); + //PI + assertTrue("deep clone PI not a clone", deepClone.getContent().get(2) != pi); + pi = null; + assertEquals("incorrect deep clone PI", "do=something",((ProcessingInstruction)deepClone.getContent().get(2)).getData()); + //entity + assertTrue("deep clone Entity not a clone", deepClone.getContent().get(3) != entity); + entity = null; + assertEquals("incorrect deep clone Entity", "wizards", ((EntityRef)deepClone.getContent().get(3)).getName()); + //text + assertEquals("incorrect deep clone test", "finally a new wand!", ((Text)deepClone.getContent().get(4)).getText()); + + + } + + /** + * Test getDocType. + */ + @Test + public void test_TCM__OrgJdomDocType_getDocType() { + Element element = new Element("element"); + DocType docType = new DocType("element"); + + Document doc = new Document(element, docType); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + + } + + /** + * Test the addition of comments to Documents. + */ + @Test + public void test_TCM__OrgJdomDocument_addContent_OrgJdomComment() { + Element element = new Element("element"); + Comment comment = new Comment("comment"); + Comment comment2 = new Comment("comment 2"); + + Document doc = new Document(element); + doc.addContent(comment); + doc.addContent(comment2); + List content = doc.getContent(); + + assertEquals("wrong number of comments in List", 3, content.size()); + assertEquals("wrong comment", comment, content.get(1)); + assertEquals("wrong comment", comment2, content.get(2)); + } + + /** + * Test the addition of ProcessingInstructions to Documents. + */ + @Test + public void test_TCM__OrgJdomDocument_addContent_OrgJdomProcessingInstruction() { + Element element = new Element("element"); + ProcessingInstruction pi = new ProcessingInstruction("test", "comment"); + ProcessingInstruction pi2 = new ProcessingInstruction("test", "comment 2"); + + Document doc = new Document(element); + doc.addContent(pi); + doc.addContent(pi2); + List content = doc.getContent(); + + assertEquals("wrong number of PI's in List", 3, content.size()); + assertEquals("wrong PI", pi, content.get(1)); + assertEquals("wrong PI", pi2, content.get(2)); + } + + /** + * Test that setRootElement works as expected. + */ + @Test + public void test_TCM__OrgJdomDocument_setRootElement_OrgJdomElement() { + Element element = new Element("element"); + + Document doc1 = new Document(element); + assertEquals("incorrect root element returned", element, doc1.getRootElement()); + Document doc2 = new Document(); + try { + doc2.setRootElement(element); + fail("didn't catch element already attached to another document"); + } + catch(IllegalAddException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test that setDocType works as expected. + */ + @Test + public void test_TCM__OrgJdomDocument_setDocType_OrgJdomDocType() { + Element element = new Element("element"); + DocType docType = new DocType("element"); + + Document doc = new Document(element); + doc.setDocType(docType); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + assertEquals("incorrect doc type returned", docType, doc.getDocType()); + } + + /** + * Test that a Document can return a root element. + */ + @Test + public void test_TCM__OrgJdomElement_getRootElement() { + Element element = new Element("element"); + + Document doc = new Document(element); + assertEquals("incorrect root element returned", element, doc.getRootElement()); + } + + /** + * Test that the toString method returns the expected result. + */ + @Test + public void test_TCM__String_toString() { + Element element = new Element("element"); + DocType docType = new DocType("element"); + + Document doc = new Document(element, docType); + String buf = new String("[Document: [DocType: ], Root is [Element: ]]"); + assertEquals("incorrect root element returned", buf, doc.toString()); + + } + + /** + * Test that an Element properly handles default namespaces + */ + @Test + public void test_TCU__testSerialization() throws IOException { + + //set up an element to test with + Element element= new Element("element", Namespace.getNamespace("http://foo")); + Element child1 = new Element("child1"); + Element child2 = new Element("child2"); + + element.addContent(child1); + element.addContent(child2); + + Document doc = new Document(element); + + + + //here is what we expect in these two scenarios + //String bufWithNoNS = ""; + + String bufWithEmptyNS = ""; + + Document docIn = UnitTestUtil.deSerialize(doc); + element = docIn.getRootElement(); + + StringWriter sw = new StringWriter(); + XMLOutputter2 op= new XMLOutputter2(Format.getRawFormat()); + op.output(element, sw); + assertTrue("Incorrect data after serialization", sw.toString().equals(bufWithEmptyNS)); + + } + + /** + * Test getContent + */ + @Test + public void test_TCM__List_getContent() { + Element element = new Element("element"); + Comment comment = new Comment("comment"); + ArrayList list = new ArrayList(); + + list.add(element); + list.add(comment); + Document doc = new Document(list); + assertEquals("missing mixed content", list, doc.getContent()); + assertEquals("wrong number of elements", 2, doc.getContent().size()); + } + + /** + * Test that setContent works according to specs. + */ + @Test + public void test_TCM__OrgJdomDocument_setContent_List() { + Element element = new Element("element"); + Element newElement = new Element("newEl"); + Comment comment = new Comment("comment"); + ProcessingInstruction pi = new ProcessingInstruction("foo", "bar"); + ArrayList list = new ArrayList(); + + list.add(newElement); + list.add(comment); + list.add(pi); + + + Document doc = new Document(element); + doc.setContent(list); + assertEquals("wrong number of elements", 3, doc.getContent().size()); + assertEquals("missing element", newElement, doc.getContent().get(0)); + assertEquals("missing comment", comment, doc.getContent().get(1)); + assertEquals("missing pi", pi, doc.getContent().get(2)); + } + + @Test + public void testDocumentAddDocType() { + try { + Document doc = new Document(); + doc.addContent(new Element("tag")); + List list = doc.getContent(); + list.add(new DocType("elementname")); + fail ("Should not be able to add DocType to a document after an Element"); + } catch (IllegalAddException iae) { + // good! + } catch (Exception e) { + fail ("We expect an IllegalAddException, but got " + e.getClass().getName()); + } + } + + @Test + public void testDocType() { + Document doc = new Document(); + assertTrue(doc == doc.setDocType(null)); + DocType dta = new DocType("DocTypeA"); + DocType dtb = new DocType("DocTypeB"); + doc.setDocType(dta); + assertTrue(doc.getDocType() == dta); + assertTrue(dta.getParent() == doc); + assertTrue(dtb.getParent() == null); + doc.setDocType(dtb); + assertTrue(doc.getDocType() == dtb); + assertTrue(dta.getParent() == null); + assertTrue(dtb.getParent() == doc); + try { + doc.setDocType(dtb); + fail("Should not be able to add an already attached DocType"); + } catch (IllegalAddException iae) { + // good. + } + assertTrue(doc.getDocType() == dtb); + assertTrue(dta.getParent() == null); + assertTrue(dtb.getParent() == doc); + doc.setDocType(null); + assertTrue(doc.getDocType() == null); + assertTrue(dta.getParent() == null); + assertTrue(dtb.getParent() == null); + + } + + @Test + public void testDocumentProperties() { + Document doc = new Document(); + assertTrue(doc.getProperty("one") == null); + doc.setProperty("one", "one1"); + assertTrue("one1" == doc.getProperty("one")); + doc.setProperty("two", "two2"); + assertTrue("one1" == doc.getProperty("one")); + assertTrue("two2" == doc.getProperty("two")); + } + + @Test + public void testDocumentContent() { + Document doc = new Document(); + assertTrue(doc.getContentSize() == 0); + assertTrue(doc.getDocType() == null); + assertFalse(doc.hasRootElement()); + assertTrue(doc.cloneContent().size() == 0); + + final DocType doctype = new DocType("element"); + final Comment comment1 = new Comment("comment1"); + final Comment comment2 = new Comment("comment2"); + final Element root = new Element("root"); + + doc.setDocType(doctype); + assertTrue(doc.getContentSize() == 1); + assertTrue(doc.getDocType() == doctype); + assertFalse(doc.hasRootElement()); + assertTrue(doc.cloneContent().size() == 1); + assertTrue(doc.cloneContent().get(0) instanceof DocType); + assertTrue(doc.indexOf(doctype) == 0); + + assertTrue(doctype.getParent() == doc); + assertTrue(doc.removeContent().get(0) == doctype); + + assertTrue(doctype.getParent() == null); + assertTrue(doc.getContentSize() == 0); + assertTrue(doc.getDocType() == null); + assertFalse(doc.hasRootElement()); + assertTrue(doc.cloneContent().size() == 0); + + doc.addContent(comment1); + doc.addContent(comment2); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == null); + assertTrue(doc.getContentSize() == 2); + doc.setContent(1, doctype); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == null); + assertTrue(doctype.getParent() == doc); + assertTrue(doc.getContentSize() == 2); + + doc.setContent(comment2); + assertTrue(comment1.getParent() == null); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == null); + assertTrue(doc.getContentSize() == 1); + assertTrue(comment2 == doc.removeContent(0)); + + doc.addContent(Collections.singleton(comment2)); + assertTrue(comment1.getParent() == null); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == null); + assertTrue(doc.getContentSize() == 1); + + doc.addContent(0, Collections.singleton(comment1)); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == null); + assertTrue(doc.getContentSize() == 2); + + doc.addContent(2, Collections.singleton(doctype)); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == doc); + assertTrue(doc.getContentSize() == 3); + assertTrue(doc.indexOf(comment1) == 0); + assertTrue(doc.indexOf(comment2) == 1); + assertTrue(doc.indexOf(doctype) == 2); + + doc.setContent(2, Collections.singleton(doctype)); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == doc); + assertTrue(doc.getContentSize() == 3); + assertTrue(doc.indexOf(comment1) == 0); + assertTrue(doc.indexOf(comment2) == 1); + assertTrue(doc.indexOf(doctype) == 2); + + Set empty = Collections.emptySet(); + doc.setContent(2, empty); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == null); + assertTrue(doc.getContentSize() == 2); + assertTrue(doc.indexOf(comment1) == 0); + assertTrue(doc.indexOf(comment2) == 1); + assertTrue(doc.indexOf(doctype) == -1); + + doc.addContent(2, doctype); + assertTrue(comment1.getParent() == doc); + assertTrue(comment2.getParent() == doc); + assertTrue(doctype.getParent() == doc); + assertTrue(doc.getContentSize() == 3); + assertTrue(doc.indexOf(comment1) == 0); + assertTrue(doc.indexOf(comment2) == 1); + assertTrue(doc.indexOf(doctype) == 2); + + doc.addContent(root); + assertTrue(doc.indexOf(root) == 3); + assertTrue(doc.getContentSize() == 4); + assertTrue(doc.getRootElement() == root); + assertTrue(root.getParent() == doc); + assertTrue(doc.getContent().size() == 4); + assertTrue(doc.getContent(new ElementFilter()).size() == 1); + assertTrue(doc.getContent(new ContentFilter(ContentFilter.COMMENT)).size() == 2); + assertTrue(root == doc.detachRootElement()); + assertTrue(null == doc.detachRootElement()); + + try { + doc.getContent(); + fail("Should not be able to get content when there's no root element"); + } catch (IllegalStateException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalStateException , not " + e.getClass().getName()); + } + + try { + doc.getContent(new ElementFilter()); + fail("Should not be able to get content when there's no root element"); + } catch (IllegalStateException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalStateException , not " + e.getClass().getName()); + } + + assertTrue(doc.removeContent(new ContentFilter(ContentFilter.COMMENT)).size() == 2); + + // at this point, all the Document has is a doctype + assertTrue(doc.getContentSize() == 1); + assertTrue(doc.getDocType() == doctype); + + // time for more broken content. + try { + doc.addContent(0, root); + fail("Should not be able to put root element before the doctype"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + doc.addContent(1, root); + doc.setDocType(null); + try { + doc.addContent(1, doctype); + fail("Should not be able to put doctype after the root"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + doc.setDocType(doctype); + + try { + doc.addContent(doc.indexOf(doctype) + 1, new DocType("anotherdup")); + fail("Should not be able to add second doctype"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + doc.addContent(1, comment1); + + try { + doc.setContent(1, new DocType("anotherdup")); + fail("Should not be able to set second doctype"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + try { + doc.setContent(1, new Element("anotherdup")); + fail("Should not be able to set second root element"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + // but you should be able to replace the root element + assertTrue(doc == doc.setContent(doc.indexOf(root), new Element("root2"))); + // and you should be able to replace the doctype + assertTrue(doc == doc.setContent(doc.indexOf(doctype), new DocType("doctype2"))); + // and you should be able to set the root element if there's no other root element. + assertTrue(doc.removeContent(doc.getRootElement())); + assertTrue(doc == doc.setContent(doc.indexOf(comment1), root)); + + + try { + doc.addContent(new EntityRef("myref")); + fail("Should not be able to add EntityRef"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + try { + doc.addContent(new CDATA("mycdata")); + fail("Should not be able to add CDATA"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + try { + doc.addContent(new Text("text")); + fail("Should not be able to add Text"); + } catch (IllegalAddException ise) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException , not " + e.getClass().getName()); + } + + + + } + + @Test + public void testClone() { + Document doc = new Document(); + final DocType doctype = new DocType("element"); + final Comment comment1 = new Comment("comment1"); + final Comment comment2 = new Comment("comment2"); + final ProcessingInstruction pi = new ProcessingInstruction("tstpi", "pidata"); + final Element root = new Element("root"); + + doc.setDocType(doctype); + doc.addContent(comment1); + doc.addContent(comment2); + doc.addContent(pi); + doc.addContent(root); + + Document clone = doc.clone(); + assertTrue(doc.equals(doc)); + assertTrue(clone.equals(clone)); + assertFalse(clone.equals(doc)); + assertFalse(doc.equals(clone)); + + assertTrue(doc.getContent().get(0) instanceof DocType); + assertTrue(doc.getContent().get(1) instanceof Comment); + assertTrue(doc.getContent().get(2) instanceof Comment); + assertTrue(doc.getContent().get(3) instanceof ProcessingInstruction); + assertTrue(doc.getContent().get(4) instanceof Element); + + + } + + @Test + public void testParent() { + Document doc = new Document(); + assertTrue(null == doc.getParent()); + doc.addContent(new Element("root")); + assertTrue(null == doc.getParent()); + } + + @Test + public void testToString() { + Document doc = new Document(); + assertTrue(doc.toString() != null); + assertTrue(doc.toString().indexOf("Document") >= 0); + assertTrue(doc.toString().indexOf("root") >= 0); + + doc.addContent(new Comment("tstcomment")); + assertTrue(doc.toString().indexOf("tstcomment") < 0); + assertTrue(doc.toString().indexOf("root") >= 0); + + doc.addContent(new DocType("tstdoctype")); + assertTrue(doc.toString().indexOf("Document") >= 0); + assertTrue(doc.toString().indexOf("tstcomment") < 0); + assertTrue(doc.toString().indexOf("tstdoctype") >= 0); + assertTrue(doc.toString().indexOf("root") >= 0); + + doc.addContent(new Element("tstelement")); + assertTrue(doc.toString().indexOf("Document") >= 0); + assertTrue(doc.toString().indexOf("tstcomment") < 0); + assertTrue(doc.toString().indexOf("tstdoctype") >= 0); + assertTrue(doc.toString().indexOf("root") < 0); + assertTrue(doc.toString().indexOf("tstelement") >= 0); + } + + @Test + public void testSPaceInProlog() throws IOException { + Document doc = new Document(); + + doc.addContent(new Text(" ")); + assertTrue(doc.getContentSize() == 1); + doc.addContent(new Element("root")); + assertTrue(doc.getContentSize() == 2); + doc.addContent(new Text("\n")); + assertTrue(doc.getContentSize() == 3); + doc.addContent(new Comment("foo")); + assertTrue(doc.getContentSize() == 4); + doc.addContent(new Comment("bar")); + assertTrue(doc.getContentSize() == 5); + + try { + doc.addContent(new Text("bad")); + fail("Expected an add of non-space content to fail, but it did not"); + } catch (IllegalArgumentException e) { + // good + } + + XMLOutputter2 out = new XMLOutputter2(); + out.getFormat().setLineSeparator(LineSeparator.NL); + StringWriter sw = new StringWriter(); + out.output(doc, sw); + + //System.out.println(sw.toString()); + + assertEquals("Space-in-prolog output", "\n \n\n", sw.toString()); + } + +// @Test +// public void testDocumentAddAttribute() { +// try { +// List list = (new Document(new Element("root")).getContent()); +// list.add(new Attribute("att", "value")); +// fail ("Should not be able to add Attribute to an Document's ContentList"); +// } catch (IllegalAddException iae) { +// // good! +// } catch (Exception e) { +// fail ("We expect an IllegalAddException, but got " + e.getClass().getName()); +// } +// } + +} diff --git a/test/src/java/org/jdom/test/cases/TestElement.java b/test/src/java/org/jdom/test/cases/TestElement.java new file mode 100644 index 0000000..b8ac5ac --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestElement.java @@ -0,0 +1,3322 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ + +import static org.jdom.test.util.UnitTestUtil.checkException; +import static org.jdom.test.util.UnitTestUtil.failNoException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.StringWriter; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.jdom.filter.ContentFilter; +import org.jdom.filter.ElementFilter; +import org.jdom.filter.Filters; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.IllegalAddException; +import org.jdom.IllegalNameException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.Content.CType; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public final class TestElement { + + /** + * The main method runs all the tests in the text ui + */ + public static void main(String args[]) { + JUnitCore.runClasses(TestElement.class); + } + + /** + * Test the constructor for a subclass element + */ + @Test + public void test_TCC() { + Element emt = new Element() { + // change nothing + private static final long serialVersionUID = 200L; + }; + assertNull(emt.getName()); + } + + /** + * Test the constructor for an empty element + */ + @Test + public void test_TCC___String() { + + //create a new empty element + Element el = new Element("theElement"); + assertTrue("wrong element name after constructor", el.getName().equals("theElement")); + assertTrue("expected NO_NAMESPACE", el.getNamespace().equals(Namespace.NO_NAMESPACE)); + assertTrue("expected no child elements", el.getChildren().equals(Collections.EMPTY_LIST)); + assertTrue("expected no attributes", el.getAttributes().equals(Collections.EMPTY_LIST)); + + //must have a name + try { + el = new Element(""); + fail("allowed creation of an element with no name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //name can't be null + try { + el = new Element(null); + fail("allowed creation of an element with null name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //we can assume the Verifier has been called by now so we are done + } + + /** + * Test the Element constructor with an name and namespace + */ + @Test + public void test_TCC___String_OrgJdomNamespace() { + //create a new empty element with a namespace + + Namespace ns = Namespace.getNamespace("urn:foo"); + Element el = new Element("theElement", ns); + assertTrue("wrong element name after constructor", el.getName().equals("theElement")); + assertTrue("expected urn:foo namespace", el.getNamespace().equals(Namespace.getNamespace("urn:foo"))); + assertTrue("expected no child elements", el.getChildren().equals(Collections.EMPTY_LIST)); + assertTrue("expected no attributes", el.getAttributes().equals(Collections.EMPTY_LIST)); + + //must have a name + try { + el = new Element("", ns); + fail("allowed creation of an element with no name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //name can't be null + try { + el = new Element(null, ns); + fail("allowed creation of an element with null name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //we can assume the Verifier has been called by now so we are done + } + + /** + * Test the Element constructor with a string default namespace + */ + @Test + public void test_TCC___String_String() { + //create a new empty element with a namespace + + Element el = new Element("theElement", "urn:foo"); + assertTrue("wrong element name after constructor", el.getName().equals("theElement")); + assertTrue("expected urn:foo namespace", el.getNamespace().equals(Namespace.getNamespace("urn:foo"))); + assertTrue("expected no child elements", el.getChildren().equals(Collections.EMPTY_LIST)); + assertTrue("expected no attributes", el.getAttributes().equals(Collections.EMPTY_LIST)); + + //must have a name + try { + el = new Element("", "urn:foo"); + fail("allowed creation of an element with no name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //name can't be null + try { + el = new Element(null, "urn:foo"); + fail("allowed creation of an element with null name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //we can assume the Verifier has been called by now so we are done + } + + /** + * Test the Element constructor with a namespace uri and prefix + */ + @Test + public void test_TCC___String_String_String() { + //create a new empty element with a namespace + + + Element el = new Element("theElement", "x", "urn:foo"); + assertTrue("wrong element name after constructor", el.getName().equals("theElement")); + assertTrue("expected urn:foo namespace", el.getNamespace().equals(Namespace.getNamespace("x", "urn:foo"))); + assertTrue("expected no child elements", el.getChildren().equals(Collections.EMPTY_LIST)); + assertTrue("expected no attributes", el.getAttributes().equals(Collections.EMPTY_LIST)); + + //must have a name + try { + el = new Element("", "x", "urn:foo"); + fail("allowed creation of an element with no name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //name can't be null + try { + el = new Element(null, "x", "urn:foo"); + fail("allowed creation of an element with null name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //we can assume the Verifier has been called by now so we are done + + } + + /** + * Test the equals compares only object instances + */ + @Test + public void test_TCM__boolean_equals_Object() { + Element el = new Element("theElement", "x", "urn:foo"); + Element el2 = new Element("theElement", "x", "urn:foo"); + + assertTrue("incorrect equals evaluation", ((Object) el).equals(el)); + assertTrue("incorrect equals evaluation", !((Object) el2).equals(el)); + } + + /** + * Test that hasChildren only reports true for actual child elements. + */ +/* + public void test_TCM__boolean_hasChildren() { + //set up an element to test with + Element element = new Element("element", Namespace.getNamespace("http://foo")); + assertTrue("reported children when there are none", !element.hasChildren()); + + Attribute att1 = new Attribute("anAttribute", "boo"); + element.setAttribute(att1); + assertTrue("reported children when there are none", !element.hasChildren()); + + //add some text + element.addContent("the text"); + assertTrue("reported children when there are none", !element.hasChildren()); + + //add some CDATA + element.addContent(new CDATA("the text")); + assertTrue("reported children when there are none", !element.hasChildren()); + + //add a PI + element.addContent(new ProcessingInstruction("pi", "the text")); + assertTrue("reported children when there are none", !element.hasChildren()); + + //add Comment + element.addContent(new Comment("the text")); + assertTrue("reported children when there are none", !element.hasChildren()); + + //finally a child element + Element child1 = new Element("child1"); + element.addContent(child1); + assertTrue("reported no children when there is a child element", element.hasChildren()); + } +*/ + + /** + * Test that hasMixedContent works for varying types of possible + * child and other content + */ + @Test + public void test_TCM__boolean_hasMixedContent() { + /** No op because the method is deprecated + + //set up an element to test with + Element element= new Element("element", Namespace.getNamespace("http://foo")); + assertTrue("reported mixed content when there is none", ! element.hasMixedContent()); + + + Attribute att1 = new Attribute("anAttribute", "boo"); + element.setAttribute(att1); + assertTrue("reported mixed content when there is none", ! element.hasMixedContent()); + + //add some text + element.addContent("the text"); + assertTrue("reported mixed content when there is none", ! element.hasMixedContent()); + + //add some CDATA + element.addContent(new CDATA("the text")); + assertTrue("reported no mixed content when there is", element.hasMixedContent()); + + element= new Element("element"); + //add a PI + element.addContent(new ProcessingInstruction("pi", "the text")); + assertTrue("reported mixed content when there is none", ! element.hasMixedContent()); + + //add Comment + element.addContent(new Comment("the text")); + assertTrue("reported no mixed content when there is", element.hasMixedContent()); + + element= new Element("element"); + //finally a child element + Element child1= new Element("child1"); + element.addContent(child1); + assertTrue("reported mixed content when there is none", ! element.hasMixedContent()); + + element.addContent("some text"); + assertTrue("reported no mixed content when there is", element.hasMixedContent()); + + */ + } + + /** + * Test that an Element can determine if it is a root element + */ + @Test + public void test_TCM__boolean_isRootElement() { + Element element = new Element("element"); + assertTrue("incorrectly identified element as root", !element.isRootElement()); + + new Document(element); + assertTrue("incorrectly identified element as non root", element.isRootElement()); + } + + /** + * Test than an Element can remove an Attribute by name + */ + @Test + public void test_TCM__boolean_removeAttribute_String() { + Element element = new Element("test"); + assertFalse(element.removeAttribute("att")); + Attribute att = new Attribute("anAttribute", "test"); + element.setAttribute(att); + + //make sure it's there + assertNotNull("attribute not found after add", element.getAttribute("anAttribute")); + + //and remove it + assertTrue("attribute not removed", element.removeAttribute("anAttribute")); + //make sure it's not there + assertNull("attribute found after remove", element.getAttribute("anAttribute")); + assertFalse("non-existing attribute remove returned true", element.removeAttribute("anAttribute")); + } + + /** + * Test than an Element can remove an Attribute by name + */ + @Test + public void test_TCM__boolean_removeAttribute_Attribute() { + Element element = new Element("test"); + Attribute att = new Attribute("anAttribute", "test"); + assertFalse(element.removeAttribute(att)); + + element.setAttribute(att); + + //make sure it's there + assertNotNull("attribute not found after add", element.getAttribute("anAttribute")); + + //and remove it + assertTrue("attribute not removed", element.removeAttribute(att)); + //make sure it's not there + assertNull("attribute found after remove", element.getAttribute("anAttribute")); + assertFalse("non-existing attribute remove returned true", element.removeAttribute(att)); + } + + /** + * Test removeAttribute with a namespace + */ + @Test + public void test_TCM__boolean_removeAttribute_String_OrgJdomNamespace() { + Element element = new Element("test"); + Namespace ns = Namespace.getNamespace("x", "urn:test"); + assertFalse(element.removeAttribute("anAttribute", ns)); + + Attribute att = new Attribute("anAttribute", "test", ns); + element.setAttribute(att); + + //make sure it's there + assertNotNull("attribute not found after add", element.getAttribute("anAttribute", ns)); + //make sure we check namespaces - a different namespace + assertFalse("wrong attribute removed", element.removeAttribute("anAttribute", Namespace.NO_NAMESPACE)); + //and remove it + assertTrue("attribute not removed", element.removeAttribute("anAttribute", ns)); + //make sure it's not there + assertNull("attribute found after remove", element.getAttribute("anAttribute", ns)); + } + + /** + * Test removeAtttribute with a namespace uri. + */ + @Test + public void test_TCM__boolean_removeAttribute_String_String() { + /** No op because the method is deprecated + + Element element = new Element("test"); + Namespace ns = Namespace.getNamespace("x", "urn:test"); + Attribute att = new Attribute("anAttribute", "test", ns); + element.setAttribute(att); + + //make sure it's there + assertNotNull("attribute not found after add", element.getAttribute("anAttribute", ns)); + //and remove it + assertTrue("attribute not removed", element.removeAttribute("anAttribute", "urn:test")); + //make sure it's not there + assertNull("attribute found after remove", element.getAttribute("anAttribute", ns)); + */ + } + + /** + * Test removeChild by name + */ + @Test + public void test_TCM__boolean_removeChild_String() { + Element element = new Element("element"); + Element child = new Element("child"); + element.addContent(child); + + assertTrue("couldn't remove child content", element.removeChild("child")); + assertNull("child not removed", element.getChild("child")); + } + + /** + * Test removeChild by name and namespace + */ + @Test + public void test_TCM__boolean_removeChild_String_OrgJdomNamespace() { + Namespace ns = Namespace.getNamespace("x", "urn:fudge"); + Element element = new Element("element"); + Element child = new Element("child", ns); + Element child2 = new Element("child", ns); + element.addContent(child); + + assertTrue("couldn't remove child content", element.removeChild("child", ns)); + assertNull("child not removed", element.getChild("child", ns)); + //now test that only the first child is removed + element.addContent(child); + element.addContent(child2); + assertTrue("couldn't remove child content", element.removeChild("child", ns)); + assertNotNull("child not removed", element.getChild("child", ns)); + + } + + /** + * Test removeChildren which removes all child elements + */ +/* + public void test_TCM__boolean_removeChildren() { + Namespace ns = Namespace.getNamespace("x", "urn:fudge"); + Element element = new Element("element"); + + assertTrue("incorrectly returned true when deleting no content", element.removeChildren() == false); + + Element child = new Element("child", ns); + Element child2 = new Element("child", ns); + element.addContent(child); + element.addContent(child2); + + assertTrue("couldn't remove child content", element.removeChildren()); + assertNull("child not removed", element.getContent("child", ns)); + } +*/ + + /** + * Test removeChildren by name. + */ +/* + public void test_TCM__boolean_removeChildren_String() { + Element element = new Element("element"); + + assertTrue("incorrectly returned true when deleting no content", element.removeChildren() == false); + + Element child = new Element("child"); + Element child2 = new Element("child"); + element.addContent(child); + element.addContent(child2); + + assertTrue("incorrect return on bogus child", !element.removeChildren("test")); + assertNotNull("child incorrectly removed", element.getContent("child")); + assertTrue("couldn't remove child content", element.removeChildren("child")); + assertNull("children not removed", element.getContent("child")); + } +*/ + + /** + * Test removeChildren with a name and namespace + */ +/* + public void test_TCM__boolean_removeChildren_String_OrgJdomNamespace() { + Namespace ns = Namespace.getNamespace("x", "urn:fudge"); + Element element = new Element("element"); + + assertTrue("incorrectly returned true when deleting no content", element.removeChildren() == false); + + Element child = new Element("child", ns); + Element child2 = new Element("child", ns); + element.addContent(child); + element.addContent(child2); + + assertTrue("incorrect return on bogus child", !element.removeChildren("child")); + assertNotNull("child incorrectly removed", element.getContent("child", ns)); + assertTrue("couldn't remove child content", element.removeChildren("child", ns)); + assertNull("children not removed", element.getContent("child", ns)); + } +*/ + + /** + * Test removeContent for a Comment + */ + @Test + public void test_TCM__boolean_removeContent_OrgJdomComment() { + Element element = new Element("element"); + Comment comm = new Comment("a comment"); + element.addContent(comm); + + assertTrue("couldn't remove comment content", element.removeContent(comm)); + assertTrue("didn't remove comment content", element.getContent().equals(Collections.EMPTY_LIST)); + } + + /** + * Test removeContent for an Element. + */ + @Test + public void test_TCM__boolean_removeContent_OrgJdomElement() { + Element element = new Element("element"); + Element child = new Element("child"); + element.addContent(child); + + assertTrue("couldn't remove element content", element.removeContent(child)); + assertTrue("didn't remove element content", element.getContent().equals(Collections.EMPTY_LIST)); + } + + /** + * Test removeContent for entities. + */ + @Test + public void test_TCM__boolean_removeContent_OrgJdomEntity() { + Element element = new Element("element"); + EntityRef ent = new EntityRef("anEntity"); + element.addContent(ent); + + assertTrue("couldn't remove entity content", element.removeContent(ent)); + assertTrue("didn't remove entity content", element.getContent().equals(Collections.EMPTY_LIST)); + } + + /** + * Test removeContent for processing instructions. + */ + @Test + public void test_TCM__boolean_removeContent_OrgJdomProcessingInstruction() { + Element element = new Element("element"); + ProcessingInstruction pi = new ProcessingInstruction("aPi", "something"); + element.addContent(pi); + + assertTrue("couldn't remove entity content", element.removeContent(pi)); + assertTrue("didn't remove entity content", element.getContent().equals(Collections.EMPTY_LIST)); + } + + /** + * Test hashcode functions. + */ + @Test + public void test_TCM__int_hashCode() { + Element element = new Element("test"); + //only an exception would be a problem + int i = -1; + try { + i = element.hashCode(); + } + catch(Exception e) { + fail("bad hashCode"); + } + + + Element element2 = new Element("test"); + //different Elements, same text + int x = element2.hashCode(); + assertTrue("Different Elements with same value have same hashcode", x != i); + Element element3 = new Element("test2"); + //only an exception would be a problem + int y = element3.hashCode(); + assertTrue("Different Elements have same hashcode", y != x); + + } + + /** + * Test that additionalNamespaces are returned. + */ + @Test + public void test_TCM__List_getAdditionalNamespaces() { + Element element = new Element("element"); + element.addNamespaceDeclaration(Namespace.getNamespace("x", "urn:foo")); + element.addNamespaceDeclaration(Namespace.getNamespace("y", "urn:bar")); + element.addNamespaceDeclaration(Namespace.getNamespace("z", "urn:baz")); + + List list = element.getAdditionalNamespaces(); + + Namespace ns = list.get(0); + assertTrue("didn't return added namespace", ns.getURI().equals("urn:foo")); + ns = list.get(1); + assertTrue("didn't return added namespace", ns.getURI().equals("urn:bar")); + ns = list.get(2); + assertTrue("didn't return added namespace", ns.getURI().equals("urn:baz")); + } + + /** + * Test that getAttribute returns all attributes of this element. + */ + @Test + public void test_TCM__List_getAttributes() { + Element element = new Element("test"); + Attribute att = new Attribute("anAttribute", "test"); + Attribute att2 = new Attribute("anotherAttribute", "test"); + Attribute att3 = new Attribute("anotherAttribute", "test", Namespace.getNamespace("x", "urn:JDOM")); + element.setAttribute(att); + element.setAttribute(att2); + element.setAttribute(att3); + + List list = element.getAttributes(); + + assertEquals("incorrect size returned", list.size(), 3); + assertEquals("incorrect attribute returned", list.get(0), att); + assertEquals("incorrect attribute returned", list.get(1), att2); + } + + /** + * Test getChildren to return all children from all namespaces. + */ + @Test + public void test_TCM__List_getChildren() { + Element element = new Element("el"); + assertEquals("did not return Collections.EMPTY_LIST on empty element", Collections.EMPTY_LIST, element.getChildren("child")); + Element child1 = new Element("child"); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("child"); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + + element.addContent(child1); + element.addContent(child2); + element.addContent(child3); + + //should only get the child elements in NO_NAMESPACE + List list = element.getChildren(); + assertEquals("incorrect number of children returned", list.size(), 3); + assertEquals("incorrect child returned", list.get(0), child1); + assertEquals("incorrect child returned", list.get(1), child2); + assertEquals("incorrect child returned", list.get(2), child3); + } + + /** + * Test that Element returns a List of children by name + */ + @Test + public void test_TCM__List_getChildren_String() { + Element element = new Element("el"); + assertEquals("did not return Collections.EMPTY_LIST on empty element", Collections.EMPTY_LIST, element.getChildren("child")); + Element child1 = new Element("child"); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("child"); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + + element.addContent(child1); + element.addContent(child2); + element.addContent(child3); + + //should only get the child elements in NO_NAMESPACE + List list = element.getChildren("child"); + assertEquals("incorrect number of children returned", list.size(), 2); + assertEquals("incorrect child returned", list.get(0).getAttribute("name").getValue(), "first"); + assertEquals("incorrect child returned", list.get(1).getAttribute("name").getValue(), "second"); + } + + /** + * Test that Element returns a List of children by name and namespace + */ + @Test + public void test_TCM__List_getChildren_String_OrgJdomNamespace() { + Element element = new Element("el"); + assertEquals("did not return Collections.EMPTY_LIST on empty element", Collections.EMPTY_LIST, element.getChildren("child")); + Namespace ns = Namespace.getNamespace("urn:hogwarts"); + Element child1 = new Element("child", ns); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("child", ns); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + + element.addContent(child1); + element.addContent(child2); + element.addContent(child3); + + //should only get the child elements in the hogwarts namespace + List list = element.getChildren("child", ns); + assertEquals("incorrect number of children returned", list.size(), 2); + assertEquals("incorrect child returned", list.get(0).getAttribute("name").getValue(), "first"); + assertEquals("incorrect child returned", list.get(1).getAttribute("name").getValue(), "second"); + } + + /** + * Test that getContent returns all the content for the element + */ + @Test + public void test_TCM__List_getContent() { + Element element = new Element("el"); + assertEquals("did not return Collections.EMPTY_LIST on empty element", Collections.EMPTY_LIST, element.getContent()); + Namespace ns = Namespace.getNamespace("urn:hogwarts"); + Element child1 = new Element("child", ns); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("child", ns); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + + element.addContent(child1); + element.addContent(child2); + element.addContent(child3); + + Comment comment = new Comment("hi"); + element.addContent(comment); + CDATA cdata = new CDATA("gotcha"); + element.addContent(cdata); + ProcessingInstruction pi = new ProcessingInstruction("tester", "do=something"); + element.addContent(pi); + EntityRef entity = new EntityRef("wizards"); + element.addContent(entity); + Text text = new Text("finally a new wand!"); + element.addContent(text); + + List list = element.getContent(); + + assertEquals("incorrect number of content items", 8, list.size()); + assertEquals("wrong child element", child1, list.get(0)); + assertEquals("wrong child element", child2, list.get(1)); + assertEquals("wrong child element", child3, list.get(2)); + assertEquals("wrong comment", comment, list.get(3)); + assertEquals("wrong CDATA", cdata, list.get(4)); + assertEquals("wrong ProcessingInstruction", pi, list.get(5)); + assertEquals("wrong EntityRef", entity, list.get(6)); + assertEquals("wrong text", text, list.get(7)); + } + + /** + * Test that clone returns a disconnected copy of the original element. + * Since clone is a deep copy, that must be tested also + */ + @Test + public void test_TCM__Object_clone() { + + //first the simple case + Element element = new Element("simple"); + Element clone = element.clone(); + + assertTrue("clone should not be the same object", element != clone); + assertEquals("clone should not have a parent", null, clone.getParent()); + assertEquals("names do not match", element.getName(), clone.getName()); + + //now do the content tests to 2 levels deep to verify recursion + element = new Element("el"); + Namespace ns = Namespace.getNamespace("urn:hogwarts"); + element.setAttribute(new Attribute("name", "anElement")); + Element child1 = new Element("child", ns); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("firstChild", ns); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + child1.addContent(child2); + element.addContent(child1); + element.addContent(child3); + + element.addNamespaceDeclaration(Namespace.getNamespace("foo", "http://test1")); + element.addNamespaceDeclaration(Namespace.getNamespace("bar", "http://test2")); + + //add mixed content to the nested child2 element + Comment comment = new Comment("hi"); + child2.addContent(comment); + CDATA cdata = new CDATA("gotcha"); + child2.addContent(cdata); + ProcessingInstruction pi = new ProcessingInstruction("tester", "do=something"); + child2.addContent(pi); + EntityRef entity = new EntityRef("wizards"); + child2.addContent(entity); + child2.addContent("finally a new wand!"); + + //a little more for the element + element.addContent("top level element text"); + + clone = element.clone(); + element = null; + child3 = null; + child2 = null; + child1 = null; + + // additional namespaces + List additional = clone.getAdditionalNamespaces(); + assertEquals("incorrect deep clone additional namespace", + additional.get(0), + Namespace.getNamespace("foo", "http://test1")); + assertEquals("incorrect deep clone additional namespace", + additional.get(1), + Namespace.getNamespace("bar", "http://test2")); + + List list = clone.getContent(); + + //finally the test + + assertEquals("wrong child element", ((Element) list.get(0)).getName(), "child"); + assertEquals("wrong child element", ((Element) list.get(1)).getName(), "child"); + Element deepClone = ((Element) list.get(0)).getChild("firstChild", Namespace.getNamespace("urn:hogwarts")); + + assertEquals("wrong nested element", "firstChild", deepClone.getName()); + //comment + assertTrue("deep clone comment not a clone", deepClone.getContent().get(0) != comment); + comment = null; + assertEquals("incorrect deep clone comment", "hi", ((Comment) deepClone.getContent().get(0)).getText()); + //CDATA + assertTrue("deep clone CDATA not a clone", ((CDATA) deepClone.getContent().get(1)).getText().equals(cdata.getText())); + cdata = null; + assertEquals("incorrect deep clone CDATA", "gotcha", ((CDATA) deepClone.getContent().get(1)).getText()); + //PI + assertTrue("deep clone PI not a clone", deepClone.getContent().get(2) != pi); + pi = null; + assertEquals("incorrect deep clone PI", "do=something", ((ProcessingInstruction) deepClone.getContent().get(2)).getData()); + //entity + assertTrue("deep clone Entity not a clone", deepClone.getContent().get(3) != entity); + entity = null; + assertEquals("incorrect deep clone EntityRef", "wizards", ((EntityRef) deepClone.getContent().get(3)).getName()); + //text + assertEquals("incorrect deep clone test", "finally a new wand!", ((Text) deepClone.getContent().get(4)).getText()); + } + + /** + * Test getAttribute by name + */ + @Test + public void test_TCM__OrgJdomAttribute_getAttribute_String() { + Element element = new Element("el"); + Attribute att = new Attribute("name", "first"); + element.setAttribute(att); + assertEquals("incorrect Attribute returned", element.getAttribute("name"), att); + } + + /** + * Test getAttribute by name and namespace + */ + @Test + public void test_TCM__OrgJdomAttribute_getAttribute_String_OrgJdomNamespace() { + Element element = new Element("el"); + Namespace ns = Namespace.getNamespace("x", "urn:foo"); + Attribute att = new Attribute("name", "first", ns); + element.setAttribute(att); + element.setAttribute(new Attribute("name", "first")); + assertEquals("incorrect Attribute returned", element.getAttribute("name", ns), att); + + } + + /** + * Test that an element returns the reference to it's enclosing document + */ + @Test + public void test_TCM__OrgJdomDocument_getDocument() { + Element element = new Element("element"); + + assertNull("incorrectly returned document before there is one", element.getDocument()); + + Element child = new Element("child"); + Element child2 = new Element("child"); + element.addContent(child); + element.addContent(child2); + + Document doc = new Document(element); + + assertEquals("didn't return correct Document", element.getDocument(), doc); + assertEquals("didn't return correct Document", child.getDocument(), doc); + } + + /** + * Test addContent for CDATA. + */ + @Test + public void test_TCM__OrgJdomElement_addContent_OrgJdomCDATA() { + //positive test is covered in test__List_getContent() + + //test with null + Element element = new Element("element"); + CDATA cdata = null; + try { + element.addContent(cdata); + fail("didn't catch null CDATA element"); + } + catch (NullPointerException e) { + // this is what List interface expects + } + catch (Exception e) { + fail("Expect NPE, not Exception " + e.getClass().getName() + + ": " + e.getMessage()); + } + } + + /** + * Test adding comment content. + */ + @Test + public void test_TCM__OrgJdomElement_addContent_OrgJdomComment() { + Element element = new Element("element"); + Comment comm = new Comment("a comment"); + element.addContent(comm); + + assertEquals("didn't add comment content", element.getContent().get(0), comm); + try { + comm = null; + element.addContent(comm); + fail("didn't catch null Comment"); + } + catch (NullPointerException e) { + // OK + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test addContent for Element. + */ + @Test + public void test_TCM__OrgJdomElement_addContent_OrgJdomElement() { + //positive test is covered in test__List_getContent() + + //test with null + Element element = new Element("element"); + try { + Element el = null; + element.addContent(el); + fail("didn't catch null Element"); + } + catch (NullPointerException e) { + // OK + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test addContent for Entity + */ + @Test + public void test_TCM__OrgJdomElement_addContent_OrgJdomEntityRef() { + //positive test is covered in test__List_getContent() + + //try with null + Element element = new Element("element"); + try { + EntityRef entity = null; + element.addContent(entity); + fail("didn't catch null EntityRef"); + } + catch (NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test addContent for ProcessingInstruction. + */ + @Test + public void test_TCM__OrgJdomElement_addContent_OrgJdomProcessingInstruction() { + //positive test is covered in test__List_getContent() + + //try with null data + Element element = new Element("element"); + try { + ProcessingInstruction pi = null; + element.addContent(pi); + fail("didn't catch null ProcessingInstruction"); + } + catch (NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test that string based content is added correctly to an Element. + */ + @Test + public void test_TCM__OrgJdomElement_addContent_String() { + Element element = new Element("element"); + element.addContent("the first part"); + assertEquals("incorrect string value", "the first part", element.getText()); + element.addContent("the second part"); + assertEquals("incorrect string value", "the first partthe second part", element.getText()); + //make sure it still works with mixed content + element.addContent(new Element("child")); + element.addContent("the third part"); + assertEquals("incorrect string value", "the first partthe second partthe third part", element.getText()); + + // RolfL: I'v tried not to mess with the 'original' JUnit tests, but the + // following comment does not match the code, or the tests. + // vvvvvvvvvv + //test that add content null clears the content + // ^^^^^^^^^^ + // Adding a null String appears to have always had the effect of adding + // a new 'empty' Text content...: new Text("") + // I am updating this test to match the current reality. + try { + // we expect addContent(null) to add a new Empty Text element. + // thus 'last' will be accurate after the add. + int last = element.getContentSize(); + + String data = null; + element.addContent(data); + + // ensure there is a new content at 'last', and that it is an empty string. + assertEquals("", element.getContent().get(last).getValue()); + } + catch (IllegalAddException e) { + fail("didn't handle null String content"); + } + catch (NullPointerException e) { + fail("didn't handle null String content"); + } + + } + + /** + * Test getContent by child name. + */ + @Test + public void test_TCM__OrgJdomElement_getChild_String() { + Element element = new Element("element"); + Element child = new Element("child"); + Element childNS = new Element("child", Namespace.getNamespace("urn:foo")); + element.addContent(child); + element.addContent(childNS); + assertEquals("incorrect child returned", element.getChild("child"), child); + } + + /** + * Test getContent by child name and namespace. + */ + @Test + public void test_TCM__OrgJdomElement_getChild_String_OrgJdomNamespace() { + Element element = new Element("element"); + Element child = new Element("child"); + Namespace ns = Namespace.getNamespace("urn:foo"); + Element childNS = new Element("child", ns); + element.addContent(child); + element.addContent(childNS); + assertEquals("incorrect child returned", element.getChild("child", ns), childNS); + } + + /** + * Test getCopy with only the name argument. Since the copy is just a clone, + * the clone test covers most of the testing. + */ + @Test + public void test_TCM__OrgJdomElement_getCopy_String() { + /** No op because the method is deprecated + + Element element = new Element("element", Namespace.getNamespace("urn:foo")); + element.addContent("text"); + + Element newElement = element.getCopy("new"); + assertEquals("incorrect name", "new", newElement.getName()); + assertEquals("incorrect namespace", Namespace.NO_NAMESPACE, newElement.getNamespace()); + assertEquals("incorrect content", "text", newElement.getText()); + */ + } + + /** + * Test getCopy with the name and namespace arguments. + * Since the copy is just a clone, the clone test + * covers most of the testing. + */ + @Test + public void test_TCM__OrgJdomElement_getCopy_String_OrgJdomNamespace() { + /** No op because the method is deprecated + + Element element = new Element("element", Namespace.getNamespace("urn:foo")); + element.addContent("text"); + Namespace ns = Namespace.getNamespace("urn:test:new"); + Element newElement = element.getCopy("new", ns); + assertEquals("incorrect name", "new", newElement.getName()); + assertEquals("incorrect namespace", ns, newElement.getNamespace()); + assertEquals("incorrect content", "text", newElement.getText()); + */ + } + + /** + * Test test that a child element can return it's parent. + */ + @Test + public void test_TCM__OrgJdomElement_getParent() { + Element element = new Element("el"); + Element child = new Element("child"); + element.addContent(child); + + assertEquals("parent not found", element, child.getParent()); + } + + /** + * Test addAttribute with an Attribute + */ + @Test + public void test_TCM__OrgJdomElement_setAttribute_OrgJdomAttribute() { + Element element = new Element("el"); + Attribute att = new Attribute("name", "first"); + element.setAttribute(att); + assertEquals("incorrect Attribute returned", element.getAttribute("name"), att); + + //update the value + Attribute att2 = new Attribute("name", "replacefirst"); + element.setAttribute(att2); + assertEquals("incorrect Attribute value returned", "replacefirst", element.getAttribute("name").getValue()); + + //test with bad data + try { + element.setAttribute(null); + fail("didn't catch null attribute"); + } + catch (NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + Element emt = new Element("dummy"); + Attribute ma = new Attribute("hi", "there"); + emt.setAttribute(ma); + element.setAttribute(ma); + fail("Should not be able to add already added Attribute."); + } catch (IllegalAddException iae) { + // good + } catch (Exception e) { + fail("Expect IllegalAddException, not " + e.getClass().getName()); + } + } + + /** + * Test addAttribute with a supplied name and value + */ + @Test + public void test_TCM__OrgJdomElement_setAttribute_String_String__invalidCharacters() { + final Element element = new Element("el"); + + //try with null name + try { + element.setAttribute(null, "value"); + fail("didn't catch null attribute name"); + } + catch (NullPointerException ignore) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //try with null value + try { + element.setAttribute("name2", null); + fail("didn't catch null attribute value"); + } + catch (NullPointerException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //try with bad characters + try { + element.setAttribute("\n", "value"); + fail("didn't catch bad characters in attribute name"); + } + catch (IllegalArgumentException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + element.setAttribute("name2", "" + (char) 0x01); + fail("didn't catch bad characters in attribute value"); + } + catch (IllegalArgumentException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test addAttribute with a supplied name and value + */ + @Test + public void test_setAttribute_String_String__attributeTypes() { + helper_setAttribute_String_String__attributeType(Attribute.UNDECLARED_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.CDATA_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.ID_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.IDREF_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.IDREFS_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.ENTITY_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.ENTITIES_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.NMTOKEN_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.NMTOKENS_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.NOTATION_TYPE); + helper_setAttribute_String_String__attributeType(Attribute.ENUMERATED_TYPE); + } + + /** + * Test setAttribute with a supplied name and value + */ + private void helper_setAttribute_String_String__attributeType(final AttributeType attributeType) { + final Element element = new Element("el"); + + final String attributeName = "name"; + final String attributeValue = "value"; + final String attributeNewValue = "newValue"; + + final Attribute attribute = new Attribute(attributeName, attributeValue, attributeType); + + // add attribute to element + element.setAttribute(attribute); + + final Attribute foundAttribute = element.getAttribute(attributeName); + assertNotNull("no Attribute found", foundAttribute); + + assertEquals("incorrect Attribute name returned", attributeName, foundAttribute.getName()); + assertEquals("incorrect Attribute value returned", attributeValue, foundAttribute.getValue()); + assertEquals("incorrect Attribute type returned", attributeType, foundAttribute.getAttributeType()); + + //update the value + element.setAttribute(attributeName, attributeNewValue); + + final Attribute changedAttribute = element.getAttribute(attributeName); + assertNotNull("no Attribute found", changedAttribute); + + assertEquals("incorrect Attribute name returned", attributeName, changedAttribute.getName()); + assertEquals("incorrect Attribute value returned", attributeNewValue, changedAttribute.getValue()); + assertEquals("incorrect Attribute type returned", attributeType, changedAttribute.getAttributeType()); + } + + /** + * Test addAttribute with a supplied name and value + */ + @Test + public void test_setAttribute_String_String_String__attributeTypes() { + helper_setAttribute_String_String_String__attributeType(Attribute.UNDECLARED_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.CDATA_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.ID_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.IDREF_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.IDREFS_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.ENTITY_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.ENTITIES_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.NMTOKEN_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.NMTOKENS_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.NOTATION_TYPE); + helper_setAttribute_String_String_String__attributeType(Attribute.ENUMERATED_TYPE); + } + + /** + * Test setAttribute with a supplied name and value + * + * @author Victor Toni + */ + private void helper_setAttribute_String_String_String__attributeType(final AttributeType attributeType) { + try { + final Namespace defaultNamespace = Namespace.getNamespace(null, "http://test.org/default"); + helper_setAttribute_String_String_String__attributeType(defaultNamespace, attributeType); + fail("didn't catch empty prefix for attribute "); + } + catch( final IllegalNameException ignore) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + final Namespace testNamespace = Namespace.getNamespace("test", "http://test.org/test"); + helper_setAttribute_String_String_String__attributeType(testNamespace, attributeType); + } + + /** + * Test setAttribute with a supplied name, namespace and value + * + * @author Victor Toni + */ + private void helper_setAttribute_String_String_String__attributeType(final Namespace namespace, final AttributeType attributeType) { + final Element element = new Element("el"); + + final String attributeName = "name"; + final String attributeValue = "value"; + final String attributeNewValue = "newValue"; + + final Attribute attribute = new Attribute(attributeName, attributeValue, attributeType, namespace); + + // add attribute to element + element.setAttribute(attribute); + + final Attribute foundAttribute = element.getAttribute(attributeName, namespace); + assertNotNull("no Attribute found", foundAttribute); + + assertEquals("incorrect Attribute name returned", attributeName, foundAttribute.getName()); + assertEquals("incorrect Attribute value returned", attributeValue, foundAttribute.getValue()); + assertEquals("incorrect Attribute type returned", attributeType, foundAttribute.getAttributeType()); + + //update the value + element.setAttribute(attributeName, attributeNewValue, namespace); + + final Attribute changedAttribute = element.getAttribute(attributeName, namespace); + assertNotNull("no Attribute found", changedAttribute); + + assertEquals("incorrect Attribute name returned", attributeName, changedAttribute.getName()); + assertEquals("incorrect Attribute value returned", attributeNewValue, changedAttribute.getValue()); + assertEquals("incorrect Attribute type returned", attributeType, changedAttribute.getAttributeType()); + } + + /** + * Test that attributes are added correctly as a list + */ + @Test + public void test_TCM__OrgJdomElement_setAttributes_List() { + Element element = new Element("element"); + Attribute one = new Attribute("one", "value"); + Attribute two = new Attribute("two", "value"); + Attribute three = new Attribute("three", "value"); + ArrayList list = new ArrayList(); + list.add(one); + list.add(two); + list.add(three); + + //put in an attribute that will get blown away later + element.setAttribute(new Attribute("type", "test")); + element.setAttributes(list); + assertEquals("attribute not found", one, element.getAttribute("one")); + assertEquals("attribute not found", two, element.getAttribute("two")); + assertEquals("attribute not found", three, element.getAttribute("three")); + assertNull("attribute should not have been found", element.getAttribute("type")); + + + //now try to add something that isn't an attribute which should still add those + //attributes added before the mistake. +// Element bogus = new Element("bogus"); +// Attribute four = new Attribute("four", "value"); +// +// ArrayList newList = new ArrayList(); +// newList.add(four); +// newList.add(bogus); +// try { +// element.setAttributes(newList); +// fail("didn't catch bad data in list"); +// } +// catch (ClassCastException e) { +// } + //should be an atomic operation so the original state should be preserved + assertEquals("wrong number of attributes after failed add", 3, element.getAttributes().size()); + assertEquals("attribute not found", one, element.getAttribute("one")); + assertEquals("attribute not found", two, element.getAttribute("two")); + assertEquals("attribute not found", three, element.getAttribute("three")); + + try { + element.setAttributes(null); + List attList = element.getAttributes(); + assertTrue("null didn't clear attributes", attList.size() == 0); + } + catch (IllegalArgumentException e) { + fail("didn't handle null String content"); + } + catch (NullPointerException e) { + fail("didn't handle null String content"); + } + } + + /** + * Test setting child elements in a list. + */ +/* + public void test_TCM__OrgJdomElement_setChildren_List() { + Element element = new Element("el"); + assertEquals("did not return Collections.EMPTY_LIST on empty element", Collections.EMPTY_LIST, element.getChildren("child")); + Element child1 = new Element("child"); + child1.setAttribute(new Attribute("name", "first")); + + Element child2 = new Element("child"); + child2.setAttribute(new Attribute("name", "second")); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + + ArrayList list = new ArrayList(); + list.add(child1); + list.add(child2); + list.add(child3); + + element.setChildren(list); + + //should only get the child elements in all namespaces + List contentList = element.getChildren(); + assertEquals("incorrect number of children returned", 3, contentList.size()); + assertEquals("incorrect child returned", contentList.get(0), child1); + assertEquals("incorrect child returned", contentList.get(1), child2); + assertEquals("incorrect child returned", contentList.get(2), child3); + + ArrayList newList = new ArrayList(); + newList.add(child1); + newList.add(new Attribute("name", "bogus")); + + try { + element.setChildren(newList); + fail("didn't catch a bad object in list"); + } + catch (IllegalArgumentException e) { + } + //should be an atomic operation + contentList = element.getChildren(); + assertEquals("wrong number of children after failed add", 3, contentList.size()); + assertEquals("incorrect child returned after failed add", contentList.get(0), child1); + assertEquals("incorrect child returned after failed add", contentList.get(1), child2); + assertEquals("incorrect child returned after failed add", contentList.get(2), child3); + + + //nulls should reset the list + element.setContent(null); + assertTrue("didn't reset children List", element.getContent().isEmpty()); + } +*/ + + /** + * Test adding mixed content in a List + */ + @Test + public void test_TCM__OrgJdomElement_setContent_List() { + Element element = new Element("el"); + assertEquals("did not return Collections.EMPTY_LIST on empty element", Collections.EMPTY_LIST, element.getContent()); + Namespace ns = Namespace.getNamespace("urn:hogwarts"); + Element child1 = new Element("child", ns); + + Element child2 = new Element("child", ns); + Element child3 = new Element("child", Namespace.getNamespace("ftp://wombat.stew")); + + LinkedList list = new LinkedList(); + list.add(child1); + list.add(child2); + list.add(child3); + + Comment comment = new Comment("hi"); + list.add(comment); + CDATA cdata = new CDATA("gotcha"); + list.add(cdata); + ProcessingInstruction pi = new ProcessingInstruction("tester", "do=something"); + list.add(pi); + EntityRef entity = new EntityRef("wizards"); + list.add(entity); + Text text = new Text("finally a new wand!"); + list.add(text); + + element.setContent(list); + List contentList = element.getContent(); + + assertEquals("incorrect number of content items", 8, contentList.size()); + assertEquals("wrong child element", child1, contentList.get(0)); + assertEquals("wrong child element", child2, contentList.get(1)); + assertEquals("wrong child element", child3, contentList.get(2)); + assertEquals("wrong comment", comment, contentList.get(3)); + assertEquals("wrong CDATA", cdata, contentList.get(4)); + assertEquals("wrong ProcessingInstruction", pi, contentList.get(5)); + assertEquals("wrong EntityRef", entity, contentList.get(6)); + assertEquals("wrong text", text, contentList.get(7)); + + ArrayList newList = new ArrayList(); + //test adding a bad object type in the list + newList.add(child1); + addBrokenContent(newList, new Integer(7)); + + try { + // this is a sensitive test. + // The first member in the newList is already in the element, so, + // normally adding it would cause anissue with the child already + // having a parent, and thus an IllegalAddException, so, we could + // expect that, but, setContent first detaches the previous content + // so, in this case, the child element first gets detached, and then + // re-added... so, the effect is that we get a ClassCastException + // by adding the Integer... + element.setContent(newList); + failNoException(ClassCastException.class); + } catch (Exception e) { + checkException(ClassCastException.class, e); + } + + //should add no content... if setContent fails it should restore previous + // content. + contentList = element.getContent(); + assertEquals("wrong child element after failed add", child1, contentList.get(0)); + assertEquals("wrong child element after failed add", child2, contentList.get(1)); + assertEquals("wrong child element after failed add", child3, contentList.get(2)); + assertEquals("wrong comment after failed add", comment, contentList.get(3)); + assertEquals("wrong CDATA after failed add", cdata, contentList.get(4)); + assertEquals("wrong ProcessingInstruction after failed add", pi, contentList.get(5)); + assertEquals("wrong EntityRef after failed add", entity, contentList.get(6)); + assertEquals("wrong text after failed add", text, contentList.get(7)); + + + //nulls should reset the list + element.removeContent(); + assertTrue("didn't reset mixed content List", element.getContent().isEmpty()); + assertNull(child1.getParent()); + assertNull(child2.getParent()); + assertNull(child3.getParent()); + assertNull(comment.getParent()); + assertNull(cdata.getParent()); + assertNull(pi.getParent()); + assertNull(entity.getParent()); + assertNull(text.getParent()); + + // test an empty list clears.... + newList.clear(); + assertTrue(element == element.setContent(newList)); + assertTrue(element.getContent().isEmpty()); + assertTrue(element == element.setContent(list)); + assertTrue(element.getContentSize() == list.size()); + // test a null list clears. + newList = null; + assertTrue(element == element.setContent(newList)); + assertTrue(element.getContent().isEmpty()); + assertTrue(element == element.setContent(list)); + assertTrue(element.getContentSize() == list.size()); + + // get a 1-sized list. + for (int i = list.size() - 1; i >= 1; i--) { + list.remove(i); + } + assertTrue(element == element.setContent(list)); + assertTrue(element.getContentSize() == 1); + } + + /** + * Test setting the content of the Element with just a string + * This should wipe out all other content the element has + */ + @Test + public void test_TCM__OrgJdomElement_setText_String() { + Element element = new Element("el"); + Element child = new Element("child"); + element.addContent(child); + + element.setText("it's all gone"); + assertEquals("incorrect text returned", "it's all gone", element.getText()); + assertEquals("incorrect number of content items found", 1, element.getContent().size()); + + + element.setText(null); + assertTrue("didn't clear content with null text", element.getContent().isEmpty()); + + //bad string test + + /** skip this test unless we determine that it is required + to check textual content for validity. We have the means to do + it in the validator but don't have it turned on right now for + performance reasons where the parser has already checked this + in the majority of cases. + + + try { + element.setText("test" + (char)0x01); + fail("didn't catch text with invalid character"); + } catch (IllegalArgumentException e) { + } catch (NullPointerException e) { + fail("NullPointerException"); + } + + */ + } + + /** + * Test that the Element returns it's primary namespace + */ + @Test + public void test_TCM__OrgJdomNamespace_getNamespace() { + Namespace ns = Namespace.getNamespace("urn:test:foo"); + Element element = new Element("element", ns); + + assertEquals("wrong namespace returned", ns, element.getNamespace()); + } + + /** + * Test that Element can return a namespace given a uri + */ + @Test + public void test_TCM__OrgJdomNamespace_getNamespace_String() { + Namespace ns = Namespace.getNamespace("x", "urn:test:foo"); + Element element = new Element("element", ns); + + assertEquals("wrong namespace returned", ns, element.getNamespace("x")); + assertNull("no namespace should have been found", element.getNamespace("bogus")); + + //now make sure it can return the namespace from the additional namespaces + Namespace newNs = Namespace.getNamespace("y", "urn:test:new"); + element.addNamespaceDeclaration(newNs); + assertEquals("wrong namespace returned", newNs, element.getNamespace("y")); + + //now make sure the same thing works from a child + Element child = new Element("child"); + element.addContent(child); + + assertEquals("wrong namespace returned", ns, child.getNamespace("x")); + assertNull("no namespace should have been found", child.getNamespace("bogus")); + assertEquals("wrong namespace returned", newNs, child.getNamespace("y")); + + //Now make sure we can pick up namespaces on attributes too that are not + //explicitly added with addAdditionalNamespace() + assertNull(child.getNamespace("attns")); + child.setAttribute("att", "value", Namespace.getNamespace("attns", "atturi")); + assertEquals("atturi", child.getNamespace("attns").getURI()); + child.setAttribute("att", "value", Namespace.getNamespace("attnt", "atturt")); + assertEquals("atturt", child.getNamespace("attnt").getURI()); + + assertNull(element.getNamespace("attns")); +} + + /** + * Test getAttributeValue by attribute name. + */ + @Test + public void test_TCM__String_getAttributeValue_String() { + Element element = new Element("el"); + assertEquals("incorrect value returned", element.getAttributeValue("name"), null); + element.setAttribute(new Attribute("name", "first")); + assertEquals("incorrect value returned", element.getAttributeValue("name"), "first"); + } + + /** + * Test getAttributeValue by attribute name. + */ + @Test + public void test_TCM__String_getAttributeValue_String_String() { + Element element = new Element("el"); + assertEquals("incorrect value returned", element.getAttributeValue("name", ""), ""); + element.setAttribute(new Attribute("name", "first")); + assertEquals("incorrect value returned", element.getAttributeValue("name", ""), "first"); + assertEquals("incorrect value returned", element.getAttributeValue("namex", ""), ""); + } + + /** + * Test getAttributeValue with name and namespace + */ + @Test + public void test_TCM__String_getAttributeValue_String_OrgJdomNamespace() { + Element element = new Element("el"); + assertEquals("incorrect value returned", element.getAttributeValue("name", Namespace.getNamespace("x", "urn:WombatsRUS")), null); + element.setAttribute(new Attribute("name", "first", Namespace.getNamespace("x", "urn:WombatsRUS"))); + assertEquals("incorrect value returned", element.getAttributeValue("name", Namespace.getNamespace("x", "urn:WombatsRUS")), "first"); + assertEquals("incorrect value returned", element.getAttributeValue("name", Namespace.getNamespace("y", "urn:WombatsRUS2")), null); + } + + /** + * Test getAttributeValue with name and namespace + */ + @Test + public void test_TCM__String_getAttributeValue_String_OrgJdomNamespace_String() { + Element element = new Element("el"); + assertEquals("incorrect value returned", element.getAttributeValue("name", Namespace.getNamespace("x", "urn:WombatsRUS"), ""), ""); + element.setAttribute(new Attribute("name", "first", Namespace.getNamespace("x", "urn:WombatsRUS"))); + assertEquals("incorrect value returned", element.getAttributeValue("name", Namespace.getNamespace("x", "urn:WombatsRUS"), ""), "first"); + assertEquals("incorrect value returned", element.getAttributeValue("name", Namespace.getNamespace("y", "urn:WombatsRUS2"), "x"), "x"); + } + + /** + * Test the convience method for retrieving child text. + */ + @Test + public void test_TCM__String_getChildText_String() { + Element element = new Element("element"); + Element child = new Element("child"); + child.addContent(" some text \nboo "); + element.addContent(child); + + assertEquals("incorrect text returned", " some text \nboo ", element.getChildText("child")); + } + + /** + * Test the convience method for retrieving child text for a child + * retrieved by name and namespace + */ + @Test + public void test_TCM__String_getChildText_String_OrgJdomNamespace() { + Element element = new Element("element"); + Namespace ns = Namespace.getNamespace("urn:test:foo"); + Element child = new Element("child", ns); + child.addContent(" some text \nboo "); + element.addContent(child); + + assertEquals("incorrect text returned", " some text \nboo ", element.getChildText("child", ns)); + + } + + /** + * Test the convience method for retrieving trimmed child text. + */ + @Test + public void test_TCM__String_getChildTextTrim_String() { + Element element = new Element("element"); + Element child = new Element("child"); + child.addContent(" some text \n "); + element.addContent(child); + + assertEquals("incorrect text returned", "some text", element.getChildTextTrim("child")); + } + + /** + * Test the convience method for retrieving trimmed child text for the + * child in the given namespace + */ + @Test + public void test_TCM__String_getChildTextTrim_String_OrgJdomNamespace() { + Element element = new Element("element"); + Namespace ns = Namespace.getNamespace("urn:test:foo"); + Element child = new Element("child", ns); + child.addContent(" some text \n "); + element.addContent(child); + + assertEquals("incorrect text returned", "some text", element.getChildTextTrim("child", ns)); + } + + /** + * Test getName. + */ + @Test + public void test_TCM__String_getName() { + Element element = new Element("element", Namespace.getNamespace("x", "ftp://wombat.stew")); + assertEquals("incorrect name", element.getName(), "element"); + } + + /** + * Test getNamespacePrefix. + */ + @Test + public void test_TCM__String_getNamespacePrefix() { + Element element = new Element("element", Namespace.getNamespace("x", "ftp://wombat.stew")); + assertEquals("incorrect namespace prefix", element.getNamespacePrefix(), "x"); + } + + /** + * Test code goes here. Replace this comment. + */ + @Test + public void test_TCM__String_getNamespaceURI() { + Element element = new Element("element", Namespace.getNamespace("x", "ftp://wombat.stew")); + assertEquals("incorrect uri", element.getNamespaceURI(), "ftp://wombat.stew"); + } + + /** + * Test that Element returns the correct qualified name. + */ + @Test + public void test_TCM__String_getQualifiedName() { + Element element = new Element("element", Namespace.getNamespace("x", "ftp://wombat.stew")); + assertEquals("incorrect qualified name", element.getQualifiedName(), "x:element"); + } + + /** + * Test getSerializedForm + */ + @Test + public void test_TCM__String_getSerializedForm() { +// fail("method not implemented"); + } + + /** + * Test getText returns that full text of the element + */ + @Test + public void test_TCM__String_getText() { + Element element = new Element("element"); + element.addContent(" some text \nboo "); + + assertEquals("incorrect text returned", " some text \nboo ", element.getText()); + } + + /** + * Test getTextTrim. + */ + @Test + public void test_TCM__String_getTextTrim() { + Element element = new Element("element"); + element.addContent(" some text \n "); + + assertEquals("incorrect text returned", "some text", element.getTextTrim()); + } + + /** + * Test the toString method which should return a useful string + * for debugging purposes only + */ + @Test + public void test_TCM__String_toString() { + Element element = new Element("element", Namespace.getNamespace("urn:foo")); + element.setAttribute(new Attribute("name", "aName")); + + assertEquals("wrong toString text found", "[Element: ]", element.toString()); + } + + /** + * Test addNamespaceDeclaration for prefix and uri. + */ + @Test + public void test_TCM__void_addNamespaceDeclaration_OrgJdomNamespace() { + Element element = new Element("element"); + element.addNamespaceDeclaration(Namespace.getNamespace("x", "urn:foo")); + List list = element.getAdditionalNamespaces(); + + Namespace ns = list.get(0); + assertTrue("didn't return added namespace", ns.getURI().equals("urn:foo")); + assertTrue("didn't return added namespace prefix", ns.getPrefix().equals("x")); + } + + /** + * Test that attributes will be added and retrieved according + * to specs. with namespaces and prefixes intact + * + */ + @Test + public void test_TCU__testAttributeNamespaces() { + + //set up an element and namespaces to test with + + Element el = new Element("test"); + Namespace one = Namespace.getNamespace("test", "http://foo"); + Namespace two = Namespace.getNamespace("test2", "http://foo"); + + Attribute att = new Attribute("first", "first", one); + Attribute att2 = new Attribute("first", "second", two); + + //should overwrite attributes with same names even if prefixes are different + try { + el.setAttribute(att); + el.setAttribute(att2); + assertTrue("didn't catch duplicate setAttribute with different prefixes", + el.getAttribute("first", one).getValue().equals("second")); + assertTrue("didn't catch duplicate setAttribute with different prefixes", + el.getAttribute("first", two).getNamespace().equals(two)); + } + catch (IllegalAddException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //set up some unique namespaces for later tests + Namespace testDefault = Namespace.getNamespace("http://bar"); + Namespace testNS = Namespace.getNamespace("test", "http://foo"); + Namespace testNS2 = Namespace.getNamespace("test2", "http://foo2"); + + //this should cause an error since the prefix will be discarded + Namespace testNS3; + try { + testNS3 = Namespace.getNamespace("test", ""); + fail("didn't catch bad \"\" uri name"); + } + catch (IllegalNameException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + //show how you can have an empty namespace with the current scheme + testNS3 = Namespace.getNamespace(""); + + el = new Element("test", testDefault); + el.addNamespaceDeclaration(testNS2); + try { + + el.addNamespaceDeclaration(testNS3); + } + catch (IllegalAddException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + att = new Attribute("prefixNS", "test"); + + //Test for default namespace check for attribute + att2 = new Attribute("prefixNS", "empty namespace"); + + + //test prefixes with same name but different namespaces + Attribute att3 = new Attribute("prefixNS", "test", testNS); + Attribute att4 = new Attribute("prefixNS", "test2", testNS2); + el.setAttribute(att2); + el.setAttribute(att3); + el.setAttribute(att4); + + Attribute attback = el.getAttribute("prefixNS"); + assertTrue( + "failed to get attribute from empty default namespace", + attback.getNamespaceURI().equals("")); + attback = el.getAttribute("prefixNS", testNS3); + assertTrue( + "failed to get attribute from http://bar namespace", + attback.getNamespaceURI().equals("")); + attback = el.getAttribute("prefixNS", testNS); + assertTrue( + "failed to get attribute from http://foo namespace", + attback.getNamespaceURI().equals("http://foo")); + attback = el.getAttribute("prefixNS", testNS2); + assertTrue( + "failed to get attribute from http://foo2 namespace", + attback.getNamespaceURI().equals("http://foo2")); + } + + /** + * Test that an Element properly handles default namespaces + * + */ + @Test + public void test_TCU__testDefaultNamespaces() throws IOException { + + //set up an element to test with + Element element = new Element("element", Namespace.getNamespace("http://foo")); + Element child1 = new Element("child1", Namespace.getNamespace("http://foo")); + Element child2 = new Element("child2", Namespace.getNamespace("http://foo")); + + element.addContent(child1); + element.addContent(child2); + + //here is what we expect in these two scenarios + String bufWithNoNS = ""; + + String bufWithEmptyNS = ""; + + StringWriter sw = new StringWriter(); + XMLOutputter2 op = new XMLOutputter2(); + op.output(element, sw); + assertEquals("Incorrect output for NO_NAMESPACE in a default namespace", bufWithNoNS, sw.toString()); + + //new try setting a new empty default namespace for children + element = new Element("element", Namespace.getNamespace("http://foo")); + child1 = new Element("child1"); + child2 = new Element("child2"); + + element.addContent(child1); + element.addContent(child2); + sw = new StringWriter(); + op = new XMLOutputter2(); + op.output(element, sw); + assertTrue("Incorrect output for empty default namespace", sw.toString().equals(bufWithEmptyNS)); + + + //this code tests where multiple default namespaces disallowed + try { + Element el = new Element("test"); + el.addNamespaceDeclaration(Namespace.getNamespace("", "foo:bar")); + el.addNamespaceDeclaration(Namespace.getNamespace("", "foo2:bar")); + el.addNamespaceDeclaration(Namespace.NO_NAMESPACE); + fail("didn't catch multiple default namespaces added to an element"); + + } + catch (IllegalAddException e) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + /** + * Test that Element serialization works, including with namespaces + * @throws IOException + * + */ + @Test + public void test_TCU__testSerialization() throws IOException { + + //set up an element to test with + Element element = new Element("element", Namespace.getNamespace("http://foo")); + Element child1 = new Element("child1"); + child1.setAttribute(new Attribute("anAttribute", "no namespace")); + Element child2 = new Element("child2"); + Attribute att1 = new Attribute("anAttribute", "with namespace", Namespace.getNamespace("x", "http://foo")); + child2.setAttribute(att1); + //add another child level deep + Element descendent = new Element("descendent"); + child2.addContent(descendent); + element.addContent(child1); + element.addContent(child2); + + + + //here is what we expect back after serialization + String bufWithEmptyNS = + ""; + + Element elIn = UnitTestUtil.deSerialize(element); + + StringWriter sw = new StringWriter(); + XMLOutputter2 op = new XMLOutputter2(); + op.output(elIn, sw); + assertEquals("Incorrect data after serialization", sw.toString(), bufWithEmptyNS); + + //set up an element to test with + Element element2 = new Element("element", Namespace.getNamespace("http://foo")); + element2.addNamespaceDeclaration(Namespace.getNamespace("foo", "http://test1")); + element2.addNamespaceDeclaration(Namespace.getNamespace("bar", "http://test2")); + Element child21 = new Element("child1"); + child21.setAttribute(new Attribute("anAttribute", "no namespace")); + Element child22 = new Element("child2"); + Attribute att21 = new Attribute("anAttribute", "with namespace", Namespace.getNamespace("x", "http://foo")); + child22.setAttribute(att21); + //add another child level deep + Element descendent2 = new Element("descendent"); + child22.addContent(descendent2); + element2.addContent(child21); + element2.addContent(child22); + + //here is what we expect back after serialization + String bufWithEmptyNS2 = + ""; + + Element elIn2 = UnitTestUtil.deSerialize(element2); + + StringWriter sw2 = new StringWriter(); + XMLOutputter2 op2 = new XMLOutputter2(); + op2.output(elIn2, sw2); + assertTrue("Incorrect data after serialization", sw2.toString().equals(bufWithEmptyNS2)); + } + +// @Test +// public void test_AddingString() { +// Vector v = new Vector(); +// v.add("one"); +// Element e = new Element("e"); +// try { +// e.setContent(v); +// fail("Should not be avle to add String content"); +// } catch (ClassCastException cce) { +// // good. +// } catch (Exception ex) { +// fail("Expected ClassCastException, not " + ex.getClass()); +// } +// } + + @Test + public void testElementAddDocType() { + try { + List list = (new Element("tag").getContent()); + list.add(new DocType("elementname")); + fail ("Should not be able to add DocType to an Element"); + } catch (IllegalAddException iae) { + // good! + } catch (Exception e) { + fail ("We expect an IllegalAddException, but got " + e.getClass().getName()); + } + } + +// @Test +// public void testElementAddAttribute() { +// try { +// List list = (new Element("tag").getContent()); +// list.add(new Attribute("att", "value")); +// fail ("Should not be able to add Attribute to an Element's ContentList"); +// } catch (IllegalAddException iae) { +// // good! +// } catch (Exception e) { +// fail ("We expect an IllegalAddException, but got " + e.getClass().getName()); +// } +// } + + @Test + public void testGetNamespace() { + Element emt = new Element("mine"); + Namespace nsa = Namespace.getNamespace("tstada", "uri"); + emt.setAttribute("name", "value", nsa); + assertTrue(Namespace.NO_NAMESPACE == emt.getNamespace()); + assertTrue(emt.getNamespace(null) == null); + assertTrue(emt.getNamespace("none") == null); + assertTrue(emt.getNamespace("xml") == Namespace.XML_NAMESPACE); + assertTrue(emt.getNamespace("tstada") == nsa); + } + + @Test + public void testSetNamespaceAdditional() { + Namespace nsa = Namespace.getNamespace("pfx","URIA"); + Namespace nsb = Namespace.getNamespace("pfx","URIB"); + Namespace nsc = Namespace.getNamespace("pfx","URIC"); + + Element emt = new Element("emt", nsa); + try { + // cannot add a second namespace with the same prefix as the Element. + emt.addNamespaceDeclaration(nsb); + fail("Expected Namespace Collision Exception."); + } catch (IllegalAddException iae) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalAddException, but got " + e.getClass()); + } + + // and we can change the Element's Namespace to nsc + emt.setNamespace(nsc); + // and back again. + emt.setNamespace(nsa); + + // further we can add nsa as an additional namespace because it is the + // same as the Element's namespace. + emt.addNamespaceDeclaration(nsa); + + try { + // but, now we can't change the Element's Namespace. + emt.setNamespace(nsb); + fail("Expected Namespace Collision Exception."); + } catch (IllegalAddException iae) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalAddException, but got " + e.getClass()); + } + + emt.addNamespaceDeclaration(nsa); + } + + @Test + public void testSetNamespaceAttribute() { + Namespace nsa = Namespace.getNamespace("pfx","URIA"); + Namespace nsb = Namespace.getNamespace("pfx","URIB"); + Namespace nsc = Namespace.getNamespace("pfx","URIC"); + Namespace nsd = Namespace.getNamespace("pfy","URID"); + + Element emt = new Element("emt", nsa); + try { + // cannot add a second namespace with the same prefix as the Element. + emt.setAttribute("att", "val", nsb); + fail("Expected Namespace Collision Exception."); + } catch (IllegalAddException iae) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalAddException, but got " + e.getClass()); + } + + // and we can change the Element's Namespace to nsc + emt.setNamespace(nsc); + assertTrue(nsc == emt.getNamespace()); + // and back again. + emt.setNamespace(nsa); + assertTrue(nsa == emt.getNamespace()); + + // further we can add nsa as an additional namespace because it is the + // same as the Element's namespace. + assertTrue(emt == emt.setAttribute("att", "value", nsa)); + + try { + // but, now we can't change the Element's Namespace. + emt.setNamespace(nsb); + fail("Expected Namespace Collision Exception."); + } catch (IllegalAddException iae) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalAddException, but got " + e.getClass()); + } + + // but we can change it to something with a different prefix: + emt.setNamespace(nsd); + assertTrue(nsd == emt.getNamespace()); + + assertTrue(emt.setAttribute("tst", "val1", null) == emt); + assertEquals("val1", emt.getAttributeValue("tst")); + assertEquals("val1", emt.getAttributeValue("tst", (Namespace)null)); + assertEquals("val1", emt.getAttributeValue("tst", Namespace.NO_NAMESPACE)); + assertTrue(emt.setAttribute("tst", "val2", Namespace.NO_NAMESPACE) == emt); + assertEquals("val2", emt.getAttributeValue("tst")); + assertEquals("val2", emt.getAttributeValue("tst", (Namespace)null)); + assertEquals("val2", emt.getAttributeValue("tst", Namespace.NO_NAMESPACE)); + + + } + + @Test + public void testRemoveNamespace() { + Element emt = new Element("mine"); + Namespace myuri = Namespace.getNamespace("pfx", "myuri"); + assertTrue(emt.getNamespace("pfx") == null); + emt.removeNamespaceDeclaration(myuri); + emt.addNamespaceDeclaration(myuri); + assertTrue(emt.getNamespace("pfx") == myuri); + emt.removeNamespaceDeclaration(myuri); + assertTrue(emt.getNamespace("pfx") == null); + } + + @Test + public void testGetValue() { + Element emt = new Element("root"); + emt.addContent("See "); + assertTrue("See ".equals(emt.getValue())); + emt.addContent(new Element("k1").addContent("Spot ")); + assertTrue("See Spot ".equals(emt.getValue())); + emt.addContent(new Comment("skip ")); + assertTrue("See Spot ".equals(emt.getValue())); + emt.addContent(new CDATA("run!")); + assertTrue("See Spot run!".equals(emt.getValue())); + } + + @Test + public void testGetText() { + Element emt = new Element("root"); + assertTrue("".equals(emt.getText())); + emt.addContent(new Comment("skip ")); + assertTrue("".equals(emt.getText())); + emt.addContent(new ProcessingInstruction("dummy", "nodata")); + assertTrue("".equals(emt.getText())); + emt.removeContent(); + assertTrue("".equals(emt.getText())); + emt.addContent(" See "); + assertTrue(" See ".equals(emt.getText())); + emt.addContent(new Element("k1").addContent(" Spot the \n dog ")); + assertTrue(" See ".equals(emt.getText())); + emt.addContent(new CDATA(" run! ")); + assertTrue(" See run! ".equals(emt.getText())); + assertTrue("See run!".equals(emt.getTextTrim())); + assertTrue("See run!".equals(emt.getTextNormalize())); + + assertTrue(" Spot the \n dog ".equals(emt.getChildText("k1"))); + assertTrue("Spot the \n dog".equals(emt.getChildTextTrim("k1"))); + assertTrue("Spot the dog".equals(emt.getChildTextNormalize("k1"))); + + assertTrue(" Spot the \n dog ".equals(emt.getChildText("k1", Namespace.NO_NAMESPACE))); + assertTrue("Spot the \n dog".equals(emt.getChildTextTrim("k1", Namespace.NO_NAMESPACE))); + assertTrue("Spot the dog".equals(emt.getChildTextNormalize("k1", Namespace.NO_NAMESPACE))); + + assertTrue(null == emt.getChildText("x1")); + assertTrue(null == emt.getChildTextTrim("x1")); + assertTrue(null == emt.getChildTextNormalize("x1")); + + assertTrue(null == emt.getChildText("x1", Namespace.NO_NAMESPACE)); + assertTrue(null == emt.getChildTextTrim("x1", Namespace.NO_NAMESPACE)); + assertTrue(null == emt.getChildTextNormalize("x1", Namespace.NO_NAMESPACE)); + + Namespace xx = Namespace.getNamespace("nouri"); + assertTrue(null == emt.getChildText("k1", xx)); + assertTrue(null == emt.getChildTextTrim("k1", xx)); + assertTrue(null == emt.getChildTextNormalize("k1", xx)); + } + + @Test + public void testElementContent() { + Document doc = new Document(); + Element root = new Element("root"); + assertTrue(root.getContentSize() == 0); + assertFalse(root.isRootElement()); + doc.addContent(root); + assertTrue(root.isRootElement()); + + assertTrue(root.cloneContent().size() == 0); + + final Text text = new Text("text"); + final Comment comment1 = new Comment("comment1"); + final Comment comment2 = new Comment("comment2"); + final Element child = new Element("child"); + + root.addContent(text); + assertTrue(root.getContentSize() == 1); + assertTrue(root.getText().equals(text.getText())); + assertTrue(root.cloneContent().size() == 1); + assertTrue(root.cloneContent().get(0) instanceof Text); + assertTrue(root.indexOf(text) == 0); + + assertTrue(text.getParent() == root); + assertTrue(root.removeContent().get(0) == text); + + assertTrue(text.getParent() == null); + assertTrue(root.getContentSize() == 0); + assertTrue(root.cloneContent().size() == 0); + + root.addContent(comment1); + root.addContent(comment2); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == null); + assertTrue(root.getContentSize() == 2); + root.setContent(1, text); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == null); + assertTrue(text.getParent() == root); + assertTrue(root.getContentSize() == 2); + + root.setContent(comment2); + assertTrue(comment1.getParent() == null); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == null); + assertTrue(root.getContentSize() == 1); + assertTrue(comment2 == root.removeContent(0)); + + root.addContent(Collections.singleton(comment2)); + assertTrue(comment1.getParent() == null); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == null); + assertTrue(root.getContentSize() == 1); + + root.addContent(0, Collections.singleton(comment1)); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == null); + assertTrue(root.getContentSize() == 2); + + root.addContent(2, Collections.singleton(text)); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == root); + assertTrue(root.getContentSize() == 3); + assertTrue(root.indexOf(comment1) == 0); + assertTrue(root.indexOf(comment2) == 1); + assertTrue(root.indexOf(text) == 2); + + root.setContent(2, Collections.singleton(text)); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == root); + assertTrue(root.getContentSize() == 3); + assertTrue(root.indexOf(comment1) == 0); + assertTrue(root.indexOf(comment2) == 1); + assertTrue(root.indexOf(text) == 2); + + Set empty = Collections.emptySet(); + root.setContent(2, empty); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == null); + assertTrue(root.getContentSize() == 2); + assertTrue(root.indexOf(comment1) == 0); + assertTrue(root.indexOf(comment2) == 1); + assertTrue(root.indexOf(text) == -1); + + root.addContent(2, text); + assertTrue(comment1.getParent() == root); + assertTrue(comment2.getParent() == root); + assertTrue(text.getParent() == root); + assertTrue(root.getContentSize() == 3); + assertTrue(root.indexOf(comment1) == 0); + assertTrue(root.indexOf(comment2) == 1); + assertTrue(root.indexOf(text) == 2); + + root.addContent(child); + assertTrue(root.indexOf(child) == 3); + assertTrue(root.getContentSize() == 4); + assertTrue(child.getParent() == root); + assertTrue(child.getParentElement() == root); + assertTrue(root.getContent().size() == 4); + assertTrue(root.getContent(new ElementFilter()).size() == 1); + assertTrue(root.getContent(new ContentFilter(ContentFilter.COMMENT)).size() == 2); + assertFalse(root.removeChild("child", Namespace.XML_NAMESPACE)); + assertTrue(root.removeChild("child", Namespace.NO_NAMESPACE)); + + assertTrue(root.getContentSize() == 3); + assertTrue(root.getContent(new ElementFilter()).isEmpty()); + + assertTrue(root.removeContent(new ContentFilter(ContentFilter.COMMENT)).size() == 2); + + assertTrue(root.getContentSize() == 1); + assertTrue(root == root.addContent(child)); + assertTrue(root.getContentSize() == 2); + assertFalse(root.removeChildren("nothing")); + assertFalse(root.removeChildren("child", Namespace.getNamespace("nothing"))); + assertTrue(root.removeChildren("child")); + assertTrue(root.getContentSize() == 1); + + // some negative cases not yet covered.... + try { + Element n = null; + root.addContent(0, n); + fail("Should not be able to add null Element content."); + } catch (NullPointerException npe) { + // good + } catch (Exception e) { + fail("Expected NullPointerException, but got " + e.getClass().getName()); + } + + try { + // try to add ourself. + child.addContent(0, child); + fail("Should not be able to add ourself as Element content."); + } catch (IllegalAddException npe) { + // good + } catch (Exception e) { + fail("Expected NullPointerException, but got " + e.getClass().getName()); + } + + root.detach(); + root.addContent(child); + + try { + // try to add ourself. + child.addContent(0, root); + fail("Should not be able to add circular Element content."); + } catch (IllegalAddException npe) { + // good + } catch (Exception e) { + fail("Expected NullPointerException, but got " + e.getClass().getName()); + } + } + + @Test + public void testAttributes() { + Element emt = new Element("element"); + Namespace myns = Namespace.getNamespace("pfxa", "attns"); + Namespace mynsz = Namespace.getNamespace("pfxz", "attns"); + assertTrue(emt.getAttributes().isEmpty()); + assertTrue(emt.getAttribute("att") == null); + assertTrue(emt.getAttributeValue("att") == null); + assertTrue(emt.getAttributeValue("att", Namespace.NO_NAMESPACE) == null); + assertTrue(emt.getAttributeValue("att", (Namespace)null) == null); + assertTrue(emt.getAttributeValue("att", myns) == null); + assertTrue("def".equals(emt.getAttributeValue("att", "def"))); + assertTrue("def".equals(emt.getAttributeValue("att", myns, "def"))); + assertTrue(emt.getAdditionalNamespaces().isEmpty()); + + emt.setAttribute(new Attribute("att", "val")); + assertFalse(emt.getAttributes().isEmpty()); + assertTrue(emt.getAttribute("att") != null); + assertTrue("val".equals(emt.getAttributeValue("att"))); + assertTrue("val".equals(emt.getAttributeValue("att", Namespace.NO_NAMESPACE))); + assertTrue("val".equals(emt.getAttributeValue("att", (Namespace)null))); + assertTrue(emt.getAttributeValue("att", myns) == null); + assertTrue("val".equals(emt.getAttributeValue("att", "def"))); + assertTrue("val".equals(emt.getAttributeValue("att", Namespace.NO_NAMESPACE, "def"))); + assertTrue("val".equals(emt.getAttributeValue("att", null, "def"))); + assertTrue("def".equals(emt.getAttributeValue("att", myns, "def"))); + assertTrue(emt.getAdditionalNamespaces().isEmpty()); + + emt.setAttribute(new Attribute("att", "nsval", myns)); + assertFalse(emt.getAttributes().isEmpty()); + assertTrue(emt.getAttribute("att") != null); + assertTrue(emt.getAttribute("att", myns) != null); + assertTrue(emt.getAttribute("att") != emt.getAttribute("att", myns)); + assertTrue("val".equals(emt.getAttributeValue("att"))); + assertTrue("val".equals(emt.getAttributeValue("att", Namespace.NO_NAMESPACE))); + assertTrue("val".equals(emt.getAttributeValue("att", (Namespace)null))); + assertTrue("nsval".equals(emt.getAttributeValue("att", myns))); + assertTrue("nsval".equals(emt.getAttributeValue("att", mynsz))); + assertTrue("val".equals(emt.getAttributeValue("att", "def"))); + assertTrue("nsval".equals(emt.getAttributeValue("att", myns, "def"))); + assertTrue("def".equals(emt.getAttributeValue("att", Namespace.XML_NAMESPACE, "def"))); + + assertTrue(emt.getAdditionalNamespaces().isEmpty()); + + Attribute att = emt.getAttribute("att"); + Attribute na = new Attribute("xx", "xval"); + assertFalse(emt.removeAttribute(na)); + assertTrue(emt.removeAttribute(att)); + assertTrue(att.getParent() == null); + emt.setAttribute(att); + assertTrue(att.getParent() == emt); + assertTrue(emt.setAttribute("att", "nval") == emt); + assertTrue(att.getParent() == emt); + assertTrue("nval".equals(att.getValue())); + assertTrue("nval".equals(emt.getAttributeValue("att"))); + assertTrue(emt.setAttribute(new Attribute("att", "zval")) == emt); + assertTrue(att.getParent() == null); + assertTrue("nval".equals(att.getValue())); + assertTrue("zval".equals(emt.getAttributeValue("att"))); + assertTrue(emt.setAttribute(att) == emt); + assertTrue(att.getParent() == emt); + + + att = emt.getAttribute("att", myns); + na = new Attribute("xx", "xval", myns); + assertFalse(emt.removeAttribute(na)); + assertTrue(emt.removeAttribute(att)); + assertTrue(emt.setAttribute("att", "aval", myns) == emt); + assertTrue("nsval".equals(att.getValue())); + assertTrue("aval".equals(emt.getAttributeValue("att", myns))); + assertTrue(att.getParent() == null); + emt.setAttribute(att); + assertTrue(att.getParent() == emt); + assertTrue(emt.setAttribute("att", "nval", myns) == emt); + assertTrue(att.getParent() == emt); + assertTrue("nval".equals(att.getValue())); + assertTrue("nval".equals(emt.getAttributeValue("att", myns))); + assertTrue(emt.setAttribute(new Attribute("att", "zval", myns)) == emt); + assertTrue(att.getParent() == null); + assertTrue("nval".equals(att.getValue())); + assertTrue("zval".equals(emt.getAttributeValue("att", myns))); + assertTrue(emt.setAttribute(att) == emt); + assertTrue(att.getParent() == emt); + assertTrue("nval".equals(emt.getAttributeValue("att", myns))); + + } + + @SuppressWarnings("unchecked") + private final void addBrokenContent(Collection cn, Object val) { + // this method is intentionally broken. + Collection ocn = (Collection)cn; + ocn.add(val); + } + + @Test + public void testCloneDetatchParentElement() { + Element parent = new Element("root"); + Element content = new Element("val"); + parent.addContent(content); + Element clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + + @Test + public void testContentCType() { + assertTrue(Content.CType.Element == new Element("root").getCType()); + } + + private final Comparator alphaText = new Comparator() { + @Override + public int compare(Text o1, Text o2) { + return o1.getText().compareTo(o2.getText()); + } + }; + + private final Comparator alphaContent = new Comparator() { + @Override + public int compare(Content o1, Content o2) { + final int ctd = o1.getCType().ordinal() - o2.getCType().ordinal(); + if (ctd != 0) { + return ctd; + } + final CType ct = o1.getCType(); + switch (ct) { + case Comment: + return ((Comment)o1).getText().compareTo(((Comment)o2).getText()); + case CDATA: + return ((CDATA)o1).getText().compareTo(((CDATA)o2).getText()); + case DocType: + return ((DocType)o1).getElementName() + .compareTo(((DocType)o2).getElementName()); + case Element: + return ((Element)o1).getName() + .compareTo(((Element)o2).getName()); + case ProcessingInstruction: + return ((ProcessingInstruction)o1).getTarget() + .compareTo(((ProcessingInstruction)o2).getTarget()); + case EntityRef: + return ((EntityRef)o1).getName() + .compareTo(((EntityRef)o2).getName()); + case Text: + return ((Text)o1).getText().compareTo(((Text)o2).getText()); + } + return 0; + } + }; + + @Test + public void testSort() { + Element emt = new Element("root"); + emt.addContent(new Text("d")); + emt.addContent(new Text("c")); + emt.addContent(new Text("b")); + emt.addContent(new Text("a")); + assertEquals("dcba", emt.getText()); + + emt.sortContent(Filters.text(), alphaText); + + assertEquals("abcd", emt.getText()); + } + + @Test + public void testSortOnlyContent() { + Element emt = new Element("root"); + emt.addContent(new Text("a")); + assertEquals("a", emt.getText()); + emt.sortContent(alphaContent); + assertEquals("a", emt.getText()); + } + + @Test + public void testSortOnlyFiltered() { + Element emt = new Element("root"); + emt.addContent(new Text("a")); + assertEquals("a", emt.getText()); + emt.sortContent(Filters.text(), alphaText); + assertEquals("a", emt.getText()); + } + + @Test + public void testSortAllSameContent() { + Element emt = new Element("root"); + emt.addContent(new Text("a")); + emt.addContent(new Text("a")); + emt.addContent(new Text("a")); + emt.addContent(new Text("a")); + assertEquals("aaaa", emt.getText()); + emt.sortContent(alphaContent); + assertEquals("aaaa", emt.getText()); + } + + @Test + public void testSortAllSameFiltered() { + Element emt = new Element("root"); + emt.addContent(new Text("a")); + emt.addContent(new Text("a")); + emt.addContent(new Text("a")); + emt.addContent(new Text("a")); + assertEquals("aaaa", emt.getText()); + emt.sortContent(Filters.text(), alphaText); + assertEquals("aaaa", emt.getText()); + } + + @Test + public void testSortContent() { + Element emt = new Element("root"); + CDATA cdata = new CDATA("XXX"); + emt.addContent(new Text("d")); + emt.addContent(cdata); + emt.addContent(new Text("c")); + emt.addContent(new Text("b")); + emt.addContent(new Text("a")); + assertEquals("dXXXcba", emt.getText()); + + emt.sortContent(alphaContent); + + assertEquals("abcdXXX", emt.getText()); + assertEquals(cdata, emt.getContent(4)); + } + + @Test + public void testSortContentNullComp() { + Element emt = new Element("root"); + CDATA cdata = new CDATA("XXX"); + emt.addContent(new Text("d")); + emt.addContent(cdata); + emt.addContent(new Text("c")); + emt.addContent(new Text("b")); + emt.addContent(new Text("a")); + assertEquals("dXXXcba", emt.getText()); + + emt.sortContent(null); + + assertEquals("dXXXcba", emt.getText()); + assertEquals(cdata, emt.getContent(1)); + } + + @Test + public void testSortElementContent() { + final Element emt = new Element("root"); + final CDATA cdata = new CDATA("XXX"); + final Element a = new Element("a"); + final Element b = new Element("b"); + final Element c = new Element("c"); + final Element d = new Element("d"); + + emt.addContent(d); + emt.addContent(cdata); + emt.addContent(c); + emt.addContent(b); + emt.addContent(a); + + assertTrue(emt.getContent(0) == d); + assertTrue(emt.getContent(1) == cdata); + assertTrue(emt.getContent(2) == c); + assertTrue(emt.getContent(3) == b); + assertTrue(emt.getContent(4) == a); + + emt.sortChildren(alphaContent); + assertTrue(emt.getContent(0) == a); + assertTrue(emt.getContent(1) == cdata); + assertTrue(emt.getContent(2) == b); + assertTrue(emt.getContent(3) == c); + assertTrue(emt.getContent(4) == d); + + emt.sortContent(alphaContent); + + assertTrue(emt.getContent(0) == a); + assertTrue(emt.getContent(1) == b); + assertTrue(emt.getContent(2) == c); + assertTrue(emt.getContent(3) == d); + assertTrue(emt.getContent(4) == cdata); + } + + @Test + public void testSortEqualsContent() { + Element emt = new Element("root"); + final CDATA cdata = new CDATA("XXX"); + final Text t1 = new Text("a"); + final Text t2 = new Text("a"); + final Text t3 = new Text("a"); + final Text t4 = new Text("a"); + emt.addContent(t1); + emt.addContent(cdata); + emt.addContent(t2); + emt.addContent(t3); + emt.addContent(t4); + assertEquals("aXXXaaa", emt.getText()); + + emt.sortContent(alphaContent); + + assertEquals("aaaaXXX", emt.getText()); + assertEquals(t1, emt.getContent(0)); + assertEquals(t2, emt.getContent(1)); + assertEquals(t3, emt.getContent(2)); + assertEquals(t4, emt.getContent(3)); + assertEquals(cdata, emt.getContent(4)); + } + + @Test + public void testSortInterleavedContent() { + Element emt = new Element("root"); + emt.addContent(new Text("d")); + emt.addContent(new CDATA("ZZZ")); + emt.addContent(new Text("c")); + emt.addContent(new CDATA("YYY")); + emt.addContent(new Text("b")); + emt.addContent(new CDATA("XXX")); + emt.addContent(new Text("a")); + assertEquals("dZZZcYYYbXXXa", emt.getText()); + + // we can use Text comparator for CDATA too. + emt.sortContent(Filters.cdata(), alphaText); + assertEquals("dXXXcYYYbZZZa", emt.getText()); + + // we can use Text comparator for CDATA too. + emt.sortContent(new ContentFilter(ContentFilter.TEXT), alphaContent); + assertEquals("aXXXbYYYcZZZd", emt.getText()); + + // we can use Text comparator for CDATA too.... and Filters.text() does Text and CDATA + emt.sortContent(Filters.text(), alphaText); + assertEquals("XXXYYYZZZabcd", emt.getText()); + } + + @Test + public void testSortInterleavedEqualContent() { + Element emt = new Element("root"); + final CDATA cd1 = new CDATA("ZZZ"); + final CDATA cd2 = new CDATA("ZZZ"); + final CDATA cd3 = new CDATA("ZZZ"); + emt.addContent(new Text("d")); + emt.addContent(cd1); + emt.addContent(new Text("c")); + emt.addContent(cd2); + emt.addContent(new Text("b")); + emt.addContent(cd3); + emt.addContent(new Text("a")); + assertEquals("dZZZcZZZbZZZa", emt.getText()); + + assertTrue(emt.getContent(1) == cd1); + assertTrue(emt.getContent(3) == cd2); + assertTrue(emt.getContent(5) == cd3); + + // we can use Text comparator for CDATA too. + // check sort does not reorder comp==0 content + emt.sortContent(Filters.cdata(), alphaText); + assertEquals("dZZZcZZZbZZZa", emt.getText()); + assertTrue(emt.getContent(1) == cd1); + assertTrue(emt.getContent(3) == cd2); + assertTrue(emt.getContent(5) == cd3); + + + // we can use Text comparator for CDATA too. + emt.sortContent(new ContentFilter(ContentFilter.TEXT), alphaContent); + assertEquals("aZZZbZZZcZZZd", emt.getText()); + assertTrue(emt.getContent(1) == cd1); + assertTrue(emt.getContent(3) == cd2); + assertTrue(emt.getContent(5) == cd3); + + // we can use Text comparator for CDATA too.... and Filters.text() does Text and CDATA + emt.sortContent(Filters.text(), alphaText); + assertEquals("ZZZZZZZZZabcd", emt.getText()); + assertTrue(emt.getContent(0) == cd1); + assertTrue(emt.getContent(1) == cd2); + assertTrue(emt.getContent(2) == cd3); + } + + @Test + public void testSortAttributes() { + final Element emt = new Element("root"); + final Attribute att1 = new Attribute("one", "001", Namespace.getNamespace("z", "uri1")); + final Attribute att2 = new Attribute("two", "002", Namespace.getNamespace("y", "uri1")); + final Attribute att3 = new Attribute("three", "003", Namespace.getNamespace("x", "uri1")); + final Attribute att4 = new Attribute("four", "004", Namespace.getNamespace("w", "uri2")); + final Attribute att5 = new Attribute("five", "005", Namespace.getNamespace("v", "uri2")); + + final Attribute att6 = new Attribute("six", "006", Namespace.getNamespace("x", "uri1")); + + emt.setAttribute(att5); + emt.setAttribute(att4); + emt.setAttribute(att3); + emt.setAttribute(att2); + emt.setAttribute(att1); + + checkAttOrder(emt.getAttributes(), att5, att4, att3, att2, att1); + + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + + // alphabetic by string name. + checkAttOrder(emt.getAttributes(), att5, att4, att1, att3, att2); + + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getNamespacePrefix().compareTo(o2.getNamespacePrefix()); + } + }); + + // Namespace Prefixes's are reverse order + checkAttOrder(emt.getAttributes(), att5, att4, att3, att2, att1); + + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getValue().compareTo(o2.getValue()); + } + }); + + // Values are in order + checkAttOrder(emt.getAttributes(), att1, att2, att3, att4, att5); + + // Namespace URI's have some common items.... and are in same order + // as a result, we should have no change at all. + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getNamespaceURI().compareTo(o2.getNamespaceURI()); + } + }); + + // Values are in order + checkAttOrder(emt.getAttributes(), att1, att2, att3, att4, att5); + + // Namespace URI's have some common items.... and are in same order + // as a result, we should have no change at all.... except, this time + // we do the inverse of the result... so, this moves 4&5 to the front + // but relative order is maintained for equal values.... + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return - o1.getNamespaceURI().compareTo(o2.getNamespaceURI()); + } + }); + + // Values are in order + checkAttOrder(emt.getAttributes(), att4, att5, att1, att2, att3); + + // use null sort on attributes... which sorts alphabetically by pfx:name + emt.sortAttributes(null); + + // Values are in order + checkAttOrder(emt.getAttributes(), att5, att4, att3, att2, att1); + + emt.setAttribute(att6); + + checkAttOrder(emt.getAttributes(), att5, att4, att3, att2, att1, att6); + + // alpha should sort att6 to before att3 (same prefix, six comes before three) + emt.sortAttributes(null); + + checkAttOrder(emt.getAttributes(), att5, att4, att6, att3, att2, att1); + + } + + @Test + public void testSortAttributesNone() { + Element emt = new Element("root"); + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return - o1.getNamespaceURI().compareTo(o2.getNamespaceURI()); + } + }); + // this ends up creating the Attribute array inside the Element. + checkAttOrder(emt.getAttributes()); + } + + private void checkAttOrder(List attributes, Attribute...atts) { + assertTrue(atts.length == attributes.size()); + for (int i = atts.length - 1; i >= 0; i--) { + assertTrue(atts[i] == attributes.get(i)); + } + + } + + private String getTestString() { + return " this has space "; + } + + private String getComp() { + return Format.compact(getTestString()); + } + + private String getTrim() { + return Format.trimBoth(getTestString()); + } + + private String getPlain() { + return getTestString(); + } + + private Namespace getNamespace() { + return Namespace.getNamespace("jdomtest"); + } + + public Element getTextHelperRoot() { + final Namespace ns = getNamespace(); + + final Element root = new Element("root"); + final Element childa = new Element("child"); + final Element childb = new Element("child", ns); + final Element childc = new Element("child"); + final Element childd = new Element("child", ns); + final Element childe = new Element("kid"); + final Element childf = new Element("kid", ns); + + childa.setText(getTestString()); + childb.setText(getTestString()); + childc.setText(getTestString()); + childd.setText(getTestString()); + root.addContent(childa); + root.addContent(childb); + root.addContent(childc); + root.addContent(childd); + root.addContent(childe); + root.addContent(childf); + + return root; + } + + @Test + public void testGetChildTextElementString() { + Element root = getTextHelperRoot(); + assertEquals(getPlain(), root.getChildText("child")); + assertEquals(null, root.getChildText("dummy")); + assertEquals("", root.getChildText("kid")); + } + + @Test + public void testGetChildTextElementStringNamespace() { + Element root = getTextHelperRoot(); + Namespace ns = getNamespace(); + assertEquals(getPlain(), root.getChildText("child", ns)); + assertEquals(null, root.getChildText("dummy", ns)); + assertEquals("", root.getChildText("kid", ns)); + } + + @Test + public void testGetChildTextTrimElementString() { + Element root = getTextHelperRoot(); + assertEquals(getTrim(), root.getChildTextTrim("child")); + assertEquals(null, root.getChildTextTrim("dummy")); + assertEquals("", root.getChildTextTrim("kid")); + } + + @Test + public void testGetChildTextTrimElementStringNamespace() { + Element root = getTextHelperRoot(); + Namespace ns = getNamespace(); + assertEquals(getTrim(), root.getChildTextTrim("child", ns)); + assertEquals(null, root.getChildTextTrim("dummy", ns)); + assertEquals("", root.getChildTextTrim("kid", ns)); + } + + @Test + public void testGetChildTextNormalizeElementString() { + Element root = getTextHelperRoot(); + assertEquals(getComp(), root.getChildTextNormalize("child")); + assertEquals(null, root.getChildTextNormalize("dummy")); + assertEquals("", root.getChildTextNormalize("kid")); + } + + @Test + public void testGetChildTextNormalizeElementStringNamespace() { + Element root = getTextHelperRoot(); + Namespace ns = getNamespace(); + assertEquals(getComp(), root.getChildTextNormalize("child", ns)); + assertEquals(null, root.getChildTextNormalize("dummy", ns)); + assertEquals("", root.getChildTextNormalize("kid", ns)); + } + + + @Test + public void testCoalesceTextSimple() { + Element root = new Element("root"); + root.addContent("one"); + root.addContent(" "); + root.addContent("two"); + root.addContent(" "); + root.addContent("three"); + assertTrue(5 == root.getContentSize()); + assertEquals("one two three", root.getText()); + + assertTrue(root.coalesceText(false)); + assertTrue(1 == root.getContentSize()); + assertEquals("one two three", root.getText()); + assertFalse(root.coalesceText(false)); + assertEquals("one two three", root.getText()); + } + + @Test + public void testCoalesceTextCDATA() { + Element root = new Element("root"); + root.addContent("one"); + root.addContent(" "); + root.addContent(new CDATA("two")); + root.addContent(" "); + root.addContent("three"); + assertTrue(5 == root.getContentSize()); + assertEquals("one two three", root.getText()); + + assertTrue(root.coalesceText(false)); + assertTrue(3 == root.getContentSize()); + assertEquals("one two three", root.getText()); + assertFalse(root.coalesceText(false)); + assertEquals("one two three", root.getText()); + } + + @Test + public void testCoalesceTextNested() { + Element root = new Element("root"); + root.addContent("one"); + root.addContent(" "); + Element kid = new Element("kid"); + root.addContent(kid); + kid.addContent("two"); + root.addContent(" "); + root.addContent("three"); + assertTrue(5 == root.getContentSize()); + assertEquals("one three", root.getText()); + + assertTrue(root.coalesceText(false)); + assertTrue(3 == root.getContentSize()); + assertEquals("one three", root.getText()); + assertFalse(root.coalesceText(false)); + assertEquals("one three", root.getText()); + } + + @Test + public void testCoalesceTextEmpty() { + Element root = new Element("root"); + root.addContent(""); + root.addContent("one"); + root.addContent(""); + root.addContent(" "); + root.addContent(""); + + root.addContent("two"); + + root.addContent(""); + root.addContent(" "); + root.addContent(""); + root.addContent("three"); + root.addContent(""); + assertTrue(11 == root.getContentSize()); + assertEquals("one two three", root.getText()); + + assertTrue(root.coalesceText(false)); + assertTrue(1 == root.getContentSize()); + assertEquals("one two three", root.getText()); + assertFalse(root.coalesceText(false)); + assertEquals("one two three", root.getText()); + } + + @Test + public void testCoalesceTextSingle() { + Element root = new Element("root"); + root.addContent(""); + assertEquals("", root.getText()); + + assertTrue(root.coalesceText(false)); + assertTrue(0 == root.getContentSize()); + assertEquals("", root.getText()); + assertFalse(root.coalesceText(false)); + assertEquals("", root.getText()); + } + + + @Test + public void testCoalesceTextSimpleRec() { + Element root = new Element("root"); + root.addContent("one"); + root.addContent(" "); + root.addContent("two"); + root.addContent(" "); + root.addContent("three"); + assertTrue(5 == root.getContentSize()); + assertEquals("one two three", root.getText()); + + assertTrue(root.coalesceText(true)); + assertTrue(1 == root.getContentSize()); + assertEquals("one two three", root.getText()); + assertFalse(root.coalesceText(true)); + assertEquals("one two three", root.getText()); + } + + @Test + public void testCoalesceTextCDATARec() { + Element root = new Element("root"); + root.addContent("one"); + root.addContent(" "); + root.addContent(new CDATA("two")); + root.addContent(" "); + root.addContent("three"); + assertTrue(5 == root.getContentSize()); + assertEquals("one two three", root.getText()); + + assertTrue(root.coalesceText(true)); + assertTrue(3 == root.getContentSize()); + assertEquals("one two three", root.getText()); + assertFalse(root.coalesceText(true)); + assertEquals("one two three", root.getText()); + } + + @Test + public void testCoalesceTextNestedRec() { + Element root = new Element("root"); + root.addContent("one"); + root.addContent(" "); + Element kid = new Element("kid"); + root.addContent(kid); + kid.addContent("two"); + root.addContent(" "); + root.addContent("three"); + assertTrue(5 == root.getContentSize()); + assertEquals("one three", root.getText()); + + assertTrue(root.coalesceText(true)); + assertTrue(3 == root.getContentSize()); + assertEquals("one three", root.getText()); + assertFalse(root.coalesceText(true)); + assertEquals("one three", root.getText()); + } + + @Test + public void testCoalesceTextEmptyRec() { + Element root = new Element("root"); + root.addContent(""); + root.addContent("one"); + root.addContent(""); + root.addContent(" "); + root.addContent(""); + + root.addContent("two"); + + root.addContent(""); + root.addContent(" "); + root.addContent(""); + root.addContent("three"); + root.addContent(""); + assertTrue(11 == root.getContentSize()); + assertEquals("one two three", root.getText()); + + assertTrue(root.coalesceText(true)); + assertTrue(1 == root.getContentSize()); + assertEquals("one two three", root.getText()); + assertFalse(root.coalesceText(true)); + assertEquals("one two three", root.getText()); + } + + @Test + public void testCoalesceTextSingleRec() { + Element root = new Element("root"); + root.addContent(""); + assertEquals("", root.getText()); + + assertTrue(root.coalesceText(true)); + assertTrue(0 == root.getContentSize()); + assertEquals("", root.getText()); + assertFalse(root.coalesceText(true)); + assertEquals("", root.getText()); + } + + + @Test + public void testXmlBaseNone() throws URISyntaxException { + Document doc = new Document(); + Element root = new Element("root"); + doc.setRootElement(root); + assertTrue(null == root.getXMLBaseURI()); + } + + @Test + public void testXmlBaseDocument() throws URISyntaxException { + Document doc = new Document(); + doc.setBaseURI("http://jdom.org/"); + Element root = new Element("root"); + doc.setRootElement(root); + URI uri = root.getXMLBaseURI(); + assertTrue(uri != null); + assertEquals("http://jdom.org/", uri.toASCIIString()); + } + + @Test + public void testXmlBaseRelative() throws URISyntaxException { + Document doc = new Document(); + Element root = new Element("root"); + doc.setRootElement(root); + root.setAttribute("base", "./sub/", Namespace.XML_NAMESPACE); + URI uri = root.getXMLBaseURI(); + assertTrue(uri != null); + assertEquals("./sub/", uri.toASCIIString()); + } + + @Test + public void testXmlBaseRelativeToDoc() throws URISyntaxException { + Document doc = new Document(); + doc.setBaseURI("http://jdom.org/"); + Element root = new Element("root"); + doc.setRootElement(root); + root.setAttribute("base", "./sub/", Namespace.XML_NAMESPACE); + URI uri = root.getXMLBaseURI(); + assertTrue(uri != null); + assertEquals("http://jdom.org/sub/", uri.toASCIIString()); + } + + @Test + public void testXmlBaseAbsolute() throws URISyntaxException { + Document doc = new Document(); + doc.setBaseURI("http://jdom.org/some/path/to/low/level"); + Element root = new Element("root"); + doc.setRootElement(root); + root.setAttribute("base", "/sub/", Namespace.XML_NAMESPACE); + URI uri = root.getXMLBaseURI(); + assertTrue(uri != null); + assertEquals("http://jdom.org/sub/", uri.toASCIIString()); + } + + @Test + public void testXmlBaseSubAbsolute() throws URISyntaxException { + Document doc = new Document(); + doc.setBaseURI("http://jdom.org/some/path/to/low/level"); + Element root = new Element("root"); + doc.setRootElement(root); + root.setAttribute("base", "http://jdom2.org/sub/", Namespace.XML_NAMESPACE); + URI uri = root.getXMLBaseURI(); + assertTrue(uri != null); + assertEquals("http://jdom2.org/sub/", uri.toASCIIString()); + } + + @Test + public void testXmlBaseBroken() { + Document doc = new Document(); + doc.setBaseURI("http://jdom.org/"); + Element root = new Element("root"); + doc.setRootElement(root); + root.setAttribute("base", "../ /sub/", Namespace.XML_NAMESPACE); + try { + root.getXMLBaseURI(); + UnitTestUtil.failNoException(URISyntaxException.class); + } catch (Exception e) { + UnitTestUtil.checkException(URISyntaxException.class, e); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestElementFilterList.java b/test/src/java/org/jdom/test/cases/TestElementFilterList.java new file mode 100644 index 0000000..eb49a24 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestElementFilterList.java @@ -0,0 +1,61 @@ +package org.jdom.test.cases; + +import java.util.List; + +import org.jdom.Content; +import org.jdom.Element; +import org.jdom.filter.ElementFilter; +import org.jdom.test.util.AbstractTestList; +import org.junit.Before; + +@SuppressWarnings("javadoc") +public class TestElementFilterList extends AbstractTestList { + + private static final Element base = new Element("dummy"); + private static final Element parent = new Element("parent").addContent(base); + + + public TestElementFilterList() { + super(Element.class, false); + } + + @Override + public List buildEmptyList() { + base.getContent().clear(); + return base.getContent(new ElementFilter()); + } + + @Override + public Element[] buildSampleContent() { + return new Element[]{ new Element("zero"), + new Element("one"), new Element("two"), + new Element("three"), new Element("four"), + new Element("five"), new Element("six")}; + } + + @Override + public Element[] buildAdditionalContent() { + return new Element[]{ new Element("seven"), + new Element("eight")}; + } + + @Override + public Object[] buildIllegalClassContent() { + Object[] ret = new Object[] {}; + return ret; + } + + @Override + public Element[] buildIllegalArgumentContent() { + return new Element[]{base, parent}; + } + + @Before + public void detatchAll () { + // make sure all content is detatched before each test. + for (Content c : buildSampleContent()) { + c.detach(); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestEntityRef.java b/test/src/java/org/jdom/test/cases/TestEntityRef.java new file mode 100644 index 0000000..ccb81da --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestEntityRef.java @@ -0,0 +1,158 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.IllegalDataException; +import org.jdom.IllegalNameException; +import org.jdom.test.util.UnitTestUtil; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestEntityRef { + + @Test + public void testEntityRef() { + EntityRef er = new EntityRef() { + // nothing + private static final long serialVersionUID = 200L; + }; + assertTrue(null == er.getPublicID()); + assertTrue(null == er.getSystemID()); + assertTrue(null == er.getName()); + } + + @Test + public void testEntityRefString() { + EntityRef er = new EntityRef("name"); + assertTrue("name".equals(er.getName())); + assertTrue(null == er.getSystemID()); + assertTrue(null == er.getPublicID()); + assertTrue(null == er.getParent()); + assertTrue(null == er.getParentElement()); + assertTrue(null == er.getDocument()); + assertTrue(null != er.toString()); + } + + @Test + public void testEntityRefStringString() { + EntityRef er = new EntityRef("name", "systemid"); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue(null == er.getPublicID()); + assertTrue(null == er.getParent()); + assertTrue(null == er.getParentElement()); + assertTrue(null == er.getDocument()); + assertTrue(null != er.toString()); + } + + @Test + public void testEntityRefStringStringString() { + EntityRef er = new EntityRef("name", "publicid", "systemid"); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + assertTrue(null == er.getParent()); + assertTrue(null == er.getParentElement()); + assertTrue(null == er.getDocument()); + assertTrue(null != er.toString()); + } + + @Test + public void testGetValue() { + assertTrue("".equals(new EntityRef("name").getValue())); + assertTrue("".equals(new EntityRef("name", "systemid").getValue())); + assertTrue("".equals(new EntityRef("name", "publicid", "systemid").getValue())); + } + + @Test + public void testSetName() { + EntityRef er = new EntityRef("name", "publicid", "systemid"); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + assertTrue(er == er.setName("myname")); + assertTrue("myname".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + } + + @Test + public void testSetPublicID() { + EntityRef er = new EntityRef("name", "publicid", "systemid"); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + assertTrue(er == er.setPublicID("mypublicid")); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("mypublicid".equals(er.getPublicID())); + } + + @Test + public void testSetSystemID() { + EntityRef er = new EntityRef("name", "publicid", "systemid"); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + assertTrue(er == er.setSystemID("mysystemid")); + assertTrue("name".equals(er.getName())); + assertTrue("mysystemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + } + + @Test + public void testToString() { + EntityRef er = new EntityRef("name", "publicid", "systemid"); + assertTrue("name".equals(er.getName())); + assertTrue("systemid".equals(er.getSystemID())); + assertTrue("publicid".equals(er.getPublicID())); + assertTrue(er.toString() != null); + } + + @Test + public void setIllegals() { + EntityRef er = new EntityRef("name", "publicid", "systemid"); + + try { + er.setName("1234"); + fail("Should throw Exception"); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("Expeced IllegalNameException, but got " + e.getClass().getName()); + } + + try { + er.setPublicID("1!2!" + (char)0x0c + "3!4"); + fail("Should throw Exception"); + } catch (IllegalDataException ine) { + // good + } catch (Exception e) { + fail("Expeced IllegalNameException, but got " + e.getClass().getName()); + } + + try { + er.setSystemID("12" + (char)0x0c + "34"); + UnitTestUtil.failNoException(IllegalDataException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalDataException.class, e); + } + } + + @Test + public void testCloneDetatchParentEntityRef() { + Element parent = new Element("root"); + EntityRef content = new EntityRef("val"); + parent.addContent(content); + EntityRef clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestFilterList.java b/test/src/java/org/jdom/test/cases/TestFilterList.java new file mode 100644 index 0000000..4438f6f --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestFilterList.java @@ -0,0 +1,1341 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import static org.junit.Assert.*; +import static org.jdom.test.util.UnitTestUtil.*; + +import org.jdom.*; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import java.util.*; + +@SuppressWarnings("javadoc") +public final class TestFilterList { + Element foo; + Element bar; + Element baz; + Element quux; + Comment comment; + Comment comment2; + Comment comment3; + Text text1; + Text text2; + Text text3; + Text text4; + + /** + * This method is called before a test is executed. + */ + @Before + public void setUp() { + foo = new Element("foo"); + bar = new Element("bar"); + baz = new Element("baz"); + quux = new Element("quux"); + comment = new Comment("comment"); + comment2 = new Comment("comment2"); + comment3 = new Comment("comment3"); + text1 = new Text("\n"); + text2 = new Text("\n"); + text3 = new Text("\n"); + text4 = new Text("\n"); + + foo.addContent(text1); + foo.addContent(bar); + foo.addContent(text2); + foo.addContent(baz); + foo.addContent(text3); + foo.addContent(comment); + foo.addContent(quux); + foo.addContent(text4); + + // Contents of foo are now: + // \n, bar, \n, baz, \n, comment, quux, \n + } + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestFilterList.class); + } + + @Test + public void test_TCM__int_hashCode() { + List content = foo.getContent(); + List content2 = new ArrayList(); + content2.addAll(content); + assertEquals("bad hashcode", content2.hashCode(), content.hashCode()); + } + + @Test + public void test_TCM__boolean_equals_Object() { + List content = foo.getContent(); + List content2 = new ArrayList(); + content2.addAll(content); + assertTrue("bad equals", content.equals(content2)); + + List children = foo.getChildren(); + List children2 = foo.getChildren(); + assertTrue("bad equals", children.equals(children2)); + } + + @Test + public void test_TCM__int_indexOf_Object() { + List children = foo.getChildren(); + assertEquals("wrong result from indexOf", 0, children.indexOf(bar)); + assertEquals("wrong result from indexOf", 1, children.indexOf(baz)); + assertEquals("wrong result from indexOf", 2, children.indexOf(quux)); + assertEquals("wrong result from indexOf", -1, children.indexOf(foo)); + assertEquals("wrong result from indexOf", -1, children.indexOf(text1)); + + List content = foo.getContent(); + assertEquals("wrong result from indexOf", 0, content.indexOf(text1)); + assertEquals("wrong result from indexOf", 1, content.indexOf(bar)); + assertEquals("wrong result from indexOf", 2, content.indexOf(text2)); + assertEquals("wrong result from indexOf", 3, content.indexOf(baz)); + assertEquals("wrong result from indexOf", 4, content.indexOf(text3)); + assertEquals("wrong result from indexOf", 5, content.indexOf(comment)); + assertEquals("wrong result from indexOf", 6, content.indexOf(quux)); + assertEquals("wrong result from indexOf", 7, content.indexOf(text4)); + assertEquals("wrong result from indexOf", -1, content.indexOf(comment2)); + assertEquals("wrong result from indexOf", -1, content.indexOf(new Integer(17))); + } + + @Test + public void test_TCM__int_lastIndexOf_Object() { + List children = foo.getChildren(); + assertEquals("wrong result from lastIndexOf", 0, children.lastIndexOf(bar)); + assertEquals("wrong result from lastIndexOf", 1, children.lastIndexOf(baz)); + assertEquals("wrong result from lastIndexOf", 2, children.lastIndexOf(quux)); + assertEquals("wrong result from lastIndexOf", -1, children.lastIndexOf(text3)); + assertEquals("wrong result from lastIndexOf", -1, children.lastIndexOf(new Integer(17))); + + List content = foo.getContent(); + assertEquals("wrong result from lastIndexOf", 0, content.lastIndexOf(text1)); + assertEquals("wrong result from lastIndexOf", 1, content.lastIndexOf(bar)); + assertEquals("wrong result from lastIndexOf", 2, content.lastIndexOf(text2)); + assertEquals("wrong result from lastIndexOf", 3, content.lastIndexOf(baz)); + assertEquals("wrong result from lastIndexOf", 4, content.lastIndexOf(text3)); + assertEquals("wrong result from lastIndexOf", 5, content.lastIndexOf(comment)); + assertEquals("wrong result from lastIndexOf", 6, content.lastIndexOf(quux)); + assertEquals("wrong result from lastIndexOf", 7, content.lastIndexOf(text4)); + assertEquals("wrong result from lastIndexOf", -1, content.lastIndexOf(comment2)); + assertEquals("wrong result from lastIndexOf", -1, content.lastIndexOf(new Integer(17))); + } + + @Test + public void test_TCM__Object_get_int() { + List children = foo.getChildren(); + assertEquals("wrong element from get", bar, children.get(0)); + assertEquals("wrong element from get", baz, children.get(1)); + assertEquals("wrong element from get", quux, children.get(2)); + + List content = foo.getContent(); + assertEquals("wrong element from get", text1, content.get(0)); + assertEquals("wrong element from get", bar, content.get(1)); + assertEquals("wrong element from get", text2, content.get(2)); + assertEquals("wrong element from get", baz, content.get(3)); + assertEquals("wrong element from get", text3, content.get(4)); + assertEquals("wrong element from get", comment, content.get(5)); + assertEquals("wrong element from get", quux, content.get(6)); + assertEquals("wrong element from get", text4, content.get(7)); + + try { + children.get(48); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + children.get(-3); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.get(48); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.get(-3); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__Object_set_int_Object() { + List children = foo.getChildren(); + List content = foo.getContent(); + + Element blah = new Element("blah"); + Text text5 = new Text("this was bar"); + content.set(1, text5); + children.set(1, blah); + + assertTrue("parent is not correct", blah.getParent() == foo); + + assertEquals("wrong size", 2, children.size()); + assertEquals("wrong element from set", baz, children.get(0)); + assertEquals("wrong element from set", blah, children.get(1)); + + assertEquals("wrong size", 8, content.size()); + assertEquals("wrong element from set", text1, content.get(0)); + assertEquals("wrong element from set", text5, content.get(1)); + assertEquals("wrong element from set", text2, content.get(2)); + assertEquals("wrong element from set", baz, content.get(3)); + assertEquals("wrong element from set", text3, content.get(4)); + assertEquals("wrong element from set", comment, content.get(5)); + assertEquals("wrong element from set", blah, content.get(6)); + assertEquals("wrong element from set", text4, content.get(7)); + + try { + children.set(48, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + children.set(-3, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + // Jump through hoops to defeat Generics + @SuppressWarnings("cast") + List tmpa = (List)children; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + tmpb.set(1, new Comment("test")); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + try { + content.set(48, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.set(-3, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + try { + // Jump through hoops to defeat Generics + @SuppressWarnings("cast") + List tmpa = (List)content; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + tmpb.set(1, new Integer(17)); + fail("Should have thrown an IllegalArgumentException"); + } catch(ClassCastException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__void_add_int_Object() { + List children = foo.getChildren(); + List content = foo.getContent(); + + Element blah = new Element("blah"); + Text text5 = new Text("this is before bar"); + content.add(1, text5); + children.add(1, blah); + + assertTrue("parent is not correct", blah.getParent() == foo); + + assertEquals("wrong size", 4, children.size()); + assertEquals("wrong element from add", bar, children.get(0)); + assertEquals("wrong element from add", blah, children.get(1)); + assertEquals("wrong element from add", baz, children.get(2)); + assertEquals("wrong element from add", quux, children.get(3)); + + assertEquals("wrong size", 10, content.size()); + assertEquals("wrong element from add", text1, content.get(0)); + assertEquals("wrong element from add", text5, content.get(1)); + assertEquals("wrong element from add", bar, content.get(2)); + assertEquals("wrong element from add", text2, content.get(3)); + assertEquals("wrong element from add", blah, content.get(4)); + assertEquals("wrong element from add", baz, content.get(5)); + assertEquals("wrong element from add", text3, content.get(6)); + assertEquals("wrong element from add", comment, content.get(7)); + assertEquals("wrong element from add", quux, content.get(8)); + assertEquals("wrong element from add", text4, content.get(9)); + + try { + children.add(48, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + children.add(-3, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + // Jump through hoops to defeat Generics + @SuppressWarnings("cast") + List tmpa = (List)children; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + tmpb.add(1, new Comment("test")); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + try { + content.add(48, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.add(-3, new Element("test")); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + // Jump through hoops to defeat Generics + @SuppressWarnings("cast") + List tmpa = (List)content; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + tmpb.add(1, new Integer(17)); + fail("Should have thrown an ClassCastException"); + } catch(ClassCastException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__boolean_add_Object() { + List children = foo.getChildren(); + List content = foo.getContent(); + + Element blah = new Element("blah"); + Text text5 = new Text("this is last"); + content.add(text5); + children.add(blah); + + assertTrue("parent is not correct", blah.getParent() == foo); + + assertEquals("wrong size", 4, children.size()); + assertEquals("wrong element from add", bar, children.get(0)); + assertEquals("wrong element from add", baz, children.get(1)); + assertEquals("wrong element from add", quux, children.get(2)); + assertEquals("wrong element from add", blah, children.get(3)); + + assertEquals("wrong size", 10, content.size()); + assertEquals("wrong element from add", text1, content.get(0)); + assertEquals("wrong element from add", bar, content.get(1)); + assertEquals("wrong element from add", text2, content.get(2)); + assertEquals("wrong element from add", baz, content.get(3)); + assertEquals("wrong element from add", text3, content.get(4)); + assertEquals("wrong element from add", comment, content.get(5)); + assertEquals("wrong element from add", quux, content.get(6)); + assertEquals("wrong element from add", text4, content.get(7)); + assertEquals("wrong element from add", text5, content.get(8)); + assertEquals("wrong element from add", blah, content.get(9)); + assertTrue("parent is not correct", comment.getParent() == foo); + + try { + // Jump through hoops to defeat Generics + @SuppressWarnings("cast") + List tmpa = (List)children; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + tmpb.add(new Comment("test")); + failNoException(IllegalAddException.class); + } catch (Exception e) { + checkException(IllegalAddException.class, e); + } + try { + // Jump through hoops to defeat Generics + @SuppressWarnings("cast") + List tmpa = (List)content; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + tmpb.add(new Integer(17)); + fail("Should have thrown an ClassCastException"); + } catch(ClassCastException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void testModification() { + List children = foo.getChildren(); + List content = foo.getContent(); + + modifyFoo(); + + // New contents of foo: + // \n, comment2, comment3, \n, \n, comment, quux, \n + + assertEquals("wrong size", 1, children.size()); + assertEquals("wrong element", quux, children.get(0)); + + assertEquals("wrong size", 7, content.size()); + assertEquals("wrong element", text1, content.get(0)); + assertEquals("wrong element", comment2, content.get(1)); + assertEquals("wrong element", comment3, content.get(2)); + assertEquals("wrong element", text3, content.get(3)); + assertEquals("wrong element", comment, content.get(4)); + assertEquals("wrong element", quux, content.get(5)); + assertEquals("wrong element", text4, content.get(6)); + + // Make sure that parentage was adjusted correctly. + assertNull("parent is not correct", bar.getParent()); + assertNull("parent is not correct", baz.getParent()); + assertTrue("parent is not correct", comment.getParent() == foo); + assertTrue("parent is not correct", comment2.getParent() == foo); + assertTrue("parent is not correct", comment3.getParent() == foo); + assertTrue("parent is not correct", quux.getParent() == foo); + } + + + @Test + public void test_TCM__int_size() { + // Test size on lists. + List children = foo.getChildren(); + assertEquals("wrong size", 3, children.size()); + List content = foo.getContent(); + assertEquals("wrong size", 8, content.size()); + + // Modify + modifyFoo(); + + // New contents of foo: + // \n, comment2, comment3, \n, \n, comment, quux, \n + + // Test size on already-created lists. + assertEquals("wrong size", 1, children.size()); + assertEquals("wrong size", 7, content.size()); + + // Test size on newly-created lists. + children = foo.getChildren(); + assertEquals("wrong size", 1, children.size()); + content = foo.getContent(); + assertEquals("wrong size", 7, content.size()); + + } + + @Test + public void testConcurrentModification() { + // Get lists. + List children = foo.getChildren(); + List content = foo.getContent(); + // Get iterators. + Iterator iter = children.iterator(); + Iterator iter2 = content.iterator(); + + // Modify + modifyFoo(); + + // Try to access an already-existing iterator. + +// Actual List implementations do not throw concurrentmod on the +// hasNext/nextIndx methods, only next() +// try { +// iter.hasNext(); +// fail("No concurrent modification exception."); +// } catch(ConcurrentModificationException ex) { +// // Do nothing +// } catch (Exception e) { +// fail("Unexpected exception " + e.getClass()); +// } + + // Try to access an already-existing iterator. + try { + iter2.next(); + fail("No concurrent modification exception."); + } catch(ConcurrentModificationException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + // Try to access a newly-created iterator. + iter = children.iterator(); + iter.hasNext(); + iter2 = content.iterator(); + iter2.next(); + + assertEquals("wrong size", 1, children.size()); + assertEquals("wrong size", 7, content.size()); + + // Test iterator.remove(). + Iterator iter3 = children.iterator(); + while(iter3.hasNext()) { + iter3.next(); + iter3.remove(); + } + + assertEquals("wrong size", 0, children.size()); + assertEquals("wrong size", 6, content.size()); + + // Test iterator.remove(). + iter2 = content.iterator(); + while(iter2.hasNext()) { + iter2.next(); + iter2.remove(); + } + + assertEquals("wrong size", 0, children.size()); + assertEquals("wrong size", 0, content.size()); + } + + // Modify "foo" a bit. + private void modifyFoo() { + List children = foo.getChildren(); + List content = foo.getContent(); + // \n, bar, \n, baz, \n, comment, quux, \n + + children.remove(1); // remove baz + assertEquals("wrong size", 2, children.size()); + assertEquals("wrong size", 7, content.size()); + // \n, bar, \n, \n, comment, quux, \n + + content.add(1, comment2); + assertEquals("wrong size", 2, children.size()); + assertEquals("wrong size", 8, content.size()); + // \n, comment2, bar, \n, \n, comment, quux, \n + + content = foo.getContent(); + + content.remove(3); // remove \n + assertEquals("wrong size", 2, children.size()); + assertEquals("wrong size", 7, content.size()); + // \n, comment2, bar, \n, comment, quux, \n + + + content.set(2, comment3); + assertEquals("wrong size", 1, children.size()); + assertEquals("wrong size", 7, content.size()); + // \n, comment2, comment3, \n, comment, quux, \n + } + + @Test + public void test_TCM__ArrayObject_toArray() { + List children = foo.getChildren(); + List content = foo.getContent(); + + Object[] childrenArray = children.toArray(); + Object[] contentArray = content.toArray(); + + // Make sure they're not live. + children.remove(1); + content.remove(comment); + + assertArrays(childrenArray, contentArray); + } + + @Test + public void test_TCM__ArrayObject_toArray_ArrayObject() { + List children = foo.getChildren(); + List content = foo.getContent(); + + // These arrays are big enough, and don't need to be expanded. + Object[] childrenArray = new Object[children.size()]; + Object[] contentArray = new Object[99]; + children.toArray(childrenArray); + content.toArray(contentArray); + + assertEquals("bad toArray size", childrenArray.length, 3); + assertEquals("bad toArray size", contentArray.length, 99); + assertArrays(childrenArray, contentArray); + + + // These arrays aren't big enough, and do need to be expanded. + childrenArray = new Object[1]; + contentArray = new Object[2]; + childrenArray = children.toArray(childrenArray); + contentArray = content.toArray(contentArray); + + // Make sure they're not live. + children.remove(baz); + content.remove(1); + + assertEquals("bad toArray size", childrenArray.length, 3); + assertEquals("bad toArray size", contentArray.length, 8); + assertArrays(childrenArray, contentArray); + } + + private void assertArrays(Object[] childrenArray, Object[] contentArray) + { + assertEquals("bad toArray", bar, childrenArray[0]); + assertEquals("bad toArray", baz, childrenArray[1]); + assertEquals("bad toArray", quux, childrenArray[2]); + + assertEquals("bad toArray", text1, contentArray[0]); + assertEquals("bad toArray", bar, contentArray[1]); + assertEquals("bad toArray", text2, contentArray[2]); + assertEquals("bad toArray", baz, contentArray[3]); + assertEquals("bad toArray", text3, contentArray[4]); + assertEquals("bad toArray", comment, contentArray[5]); + assertEquals("bad toArray", quux, contentArray[6]); + assertEquals("bad toArray", text4, contentArray[7]); + } + + @Test + public void test_TCM__boolean_contains_Object() { + List content = foo.getContent(); + List children = foo.getChildren(); + + assertTrue("bad contains", !content.contains(foo)); + assertTrue("bad contains", content.contains(bar)); + assertTrue("bad contains", content.contains(baz)); + assertTrue("bad contains", content.contains(quux)); + assertTrue("bad contains", content.contains(comment)); + assertTrue("bad contains", content.contains(text1)); + assertTrue("bad contains", content.contains(text2)); + assertTrue("bad contains", content.contains(text3)); + assertTrue("bad contains", content.contains(text4)); + assertTrue("bad contains", !content.contains(comment2)); + assertTrue("bad contains", !content.contains(new Integer(17))); + + assertTrue("bad contains", !children.contains(foo)); + assertTrue("bad contains", children.contains(bar)); + assertTrue("bad contains", children.contains(baz)); + assertTrue("bad contains", children.contains(quux)); + assertTrue("bad contains", !children.contains(comment)); + assertTrue("bad contains", !children.contains(text1)); + assertTrue("bad contains", !children.contains(text2)); + assertTrue("bad contains", !children.contains(text3)); + assertTrue("bad contains", !children.contains(text4)); + assertTrue("bad contains", !children.contains(comment2)); + assertTrue("bad contains", !children.contains(new Integer(17))); + } + + @Test + public void test_TCM__void_clear() { + List content = foo.getContent(); + List children = foo.getChildren(); + + children.clear(); + + assertEquals("bad clear", 0, children.size()); + assertEquals("bad clear", 5, content.size()); + assertTrue("bad clear", content.get(0).equals(text1)); + assertTrue("bad clear", content.get(1).equals(text2)); + assertTrue("bad clear", content.get(2).equals(text3)); + assertTrue("bad clear", content.get(3) == comment); + assertTrue("bad clear", content.get(4).equals(text4)); + + assertTrue("parent is not correct", comment.getParent() == foo); + assertNull("parent is not correct", bar.getParent()); + assertNull("parent is not correct", baz.getParent()); + assertNull("parent is not correct", quux.getParent()); + + content.clear(); + + assertTrue("bad clear", children.size() == 0); + assertTrue("bad clear", content.size() == 0); + + assertNull("parent is not correct", comment.getParent()); + assertNull("parent is not correct", bar.getParent()); + assertNull("parent is not correct", baz.getParent()); + assertNull("parent is not correct", quux.getParent()); + + } + + @Test + public void test_TCM__Object_remove_int() { + List content = foo.getContent(); + List children = foo.getChildren(); + + // \n, bar, \n, baz, \n, comment, quux, \n + content.remove(4); // third /n + children.remove(0); // bar + content.remove(3); // comment + content.remove(0); // first /n + // \n, baz, quux, \n + + assertTrue("bad removal", children.size() == 2); + assertTrue("bad removal", children.get(0) == baz); + assertTrue("bad removal", children.get(1) == quux); + assertTrue("bad removal", content.size() == 4); + assertTrue("bad removal", content.get(0).equals(text2)); + assertTrue("bad removal", content.get(1) == baz); + assertTrue("bad removal", content.get(2) == quux); + assertTrue("bad removal", content.get(3).equals(text4)); + + assertNull("parent is not correct", bar.getParent()); + assertTrue("parent is not correct", baz.getParent() == foo); + assertTrue("parent is not correct", quux.getParent() == foo); + assertNull("parent is not correct", comment.getParent()); + + try { + children.remove(48); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + children.remove(-3); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.remove(48); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.remove(-3); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__boolean_remove_Object() { + List content = foo.getContent(); + List children = foo.getChildren(); + + // contents: \n, bar, \n, baz, \n, comment, quux, \n + assertTrue("bad removal", content.remove(text1)); // first /n + assertTrue("bad removal", children.remove(bar)); // bar + assertTrue("bad removal", content.remove(comment)); // comment + assertTrue("bad removal", content.remove(text2)); // second /n + // contents: baz, \n, quux, \n + + // None of these should have any effect. + assertTrue("bad removal", !children.remove(bar)); + assertTrue("bad removal", !children.remove(text2)); + assertTrue("bad removal", !children.remove(comment2)); + assertTrue("bad removal", !children.remove(new Integer(17))); + assertTrue("bad removal", !content.remove(bar)); + assertTrue("bad removal", !content.remove(comment2)); + assertTrue("bad removal", !content.remove(new Integer(17))); + + assertTrue("bad removal", children.size() == 2); + assertTrue("bad removal", children.get(0) == baz); + assertTrue("bad removal", children.get(1) == quux); + assertTrue("bad removal", content.size() == 4); + assertTrue("bad removal", content.get(0) == baz); + assertTrue("bad removal", content.get(1).equals(text3)); + assertTrue("bad removal", content.get(2) == quux); + assertTrue("bad removal", content.get(3).equals(text4)); + + assertNull("parent is not correct", bar.getParent()); + assertTrue("parent is not correct", baz.getParent() == foo); + assertTrue("parent is not correct", quux.getParent() == foo); + assertNull("parent is not correct", comment.getParent()); + } + + @Test + public void test_TCM__boolean_isEmpty() { + List children = foo.getChildren(); + int size = children.size(); + for (int i = 0; i < size; i++) + { + assertFalse("bad isEmpty", children.isEmpty()); + children.remove(0); + } + + assertTrue("bad isEmpty", children.isEmpty()); + } + + @Test + public void test_TCM__boolean_containsAll_Collection() { + List content = foo.getContent(); + List contentList = new ArrayList(); + contentList.add(quux); + contentList.add(baz); + contentList.add(text3); + contentList.add(text1); + contentList.add(text2); + contentList.add(comment); + assertTrue("bad containsAll", content.containsAll(contentList)); + contentList.add(bar); + contentList.add(text4); + assertTrue("bad containsAll", content.containsAll(contentList)); + contentList.add(comment2); + assertFalse("bad containsAll", content.containsAll(contentList)); + + List children = foo.getChildren(); + List childrenList = new ArrayList(); + childrenList.add(baz); + assertTrue("bad containsAll", children.containsAll(childrenList)); + childrenList.add(bar); + childrenList.add(quux); + assertTrue("bad containsAll", children.containsAll(childrenList)); + childrenList.add(comment); + assertFalse("bad containsAll", children.containsAll(childrenList)); + } + + @Test + public void test_TCM__boolean_addAll_Collection() { + List content = foo.getContent(); + List addList = new ArrayList(); + addList.add(comment2); + addList.add(comment3); + content.addAll(addList); + assertEquals("bad addAll", 10, content.size()); + assertEquals("bad addAll", text1, content.get(0)); + assertEquals("bad addAll", bar, content.get(1)); + assertEquals("bad addAll", text2, content.get(2)); + assertEquals("bad addAll", baz, content.get(3)); + assertEquals("bad addAll", text3, content.get(4)); + assertEquals("bad addAll", comment, content.get(5)); + assertEquals("bad addAll", quux, content.get(6)); + assertEquals("bad addAll", text4, content.get(7)); + assertEquals("bad addAll", comment2, content.get(8)); + assertEquals("bad addAll", comment3, content.get(9)); + assertEquals("bad addAll", foo, comment2.getParent()); + assertEquals("bad addAll", foo, comment3.getParent()); + try { + content.addAll(addList); + fail("Should have thrown an IllegalArgumentException"); + } catch(IllegalArgumentException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + List children = foo.getChildren(); + List addList2 = new ArrayList(); + Element newElement = new Element("newelement"); + Element newElement2 = new Element("newelement2"); + addList2.add(newElement); + addList2.add(newElement2); + children.addAll(addList2); + assertEquals("bad addAll", 5, children.size()); + assertEquals("bad addAll", bar, children.get(0)); + assertEquals("bad addAll", baz, children.get(1)); + assertEquals("bad addAll", quux, children.get(2)); + assertEquals("bad addAll", newElement, children.get(3)); + assertEquals("bad addAll", newElement2, children.get(4)); + assertEquals("bad addAll", foo, newElement.getParent()); + assertEquals("bad addAll", foo, newElement2.getParent()); + try { + children.addAll(addList2); + fail("Should have thrown an IllegalArgumentException"); + } catch(IllegalArgumentException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__boolean_addAll_int_Collection() { + List content = foo.getContent(); + List addList = new ArrayList(); + addList.add(comment2); + addList.add(comment3); + content.addAll(2, addList); + assertEquals("bad addAll", 10, content.size()); + assertEquals("bad addAll", text1, content.get(0)); + assertEquals("bad addAll", bar, content.get(1)); + assertEquals("bad addAll", comment2, content.get(2)); + assertEquals("bad addAll", comment3, content.get(3)); + assertEquals("bad addAll", text2, content.get(4)); + assertEquals("bad addAll", baz, content.get(5)); + assertEquals("bad addAll", text3, content.get(6)); + assertEquals("bad addAll", comment, content.get(7)); + assertEquals("bad addAll", quux, content.get(8)); + assertEquals("bad addAll", text4, content.get(9)); + assertEquals("bad addAll", foo, comment2.getParent()); + assertEquals("bad addAll", foo, comment3.getParent()); + try { + content.addAll(2, addList); + fail("Should have thrown an IllegalArgumentException"); + } catch(IllegalArgumentException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + + List children = foo.getChildren(); + List addList2 = new ArrayList(); + Element newElement = new Element("newelement"); + Element newElement2 = new Element("newelement2"); + addList2.add(newElement); + addList2.add(newElement2); + children.addAll(0, addList2); + assertEquals("bad addAll", 5, children.size()); + assertEquals("bad addAll", newElement, children.get(0)); + assertEquals("bad addAll", newElement2, children.get(1)); + assertEquals("bad addAll", bar, children.get(2)); + assertEquals("bad addAll", baz, children.get(3)); + assertEquals("bad addAll", quux, children.get(4)); + assertEquals("bad addAll", foo, newElement.getParent()); + assertEquals("bad addAll", foo, newElement2.getParent()); + try { + children.addAll(0, addList2); + fail("Should have thrown an IllegalArgumentException"); + } catch(IllegalArgumentException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__boolean_removeAll_Collection() { + List content = foo.getContent(); + List removeList = new ArrayList(); + removeList.add(text4); + removeList.add(comment); + removeList.add(bar); + removeList.add(new Integer(17)); // should have no effect. + content.removeAll(removeList); + assertEquals("bad removeAll", 5, content.size()); + assertEquals("bad removeAll", text1, content.get(0)); + assertEquals("bad removeAll", text2, content.get(1)); + assertEquals("bad removeAll", baz, content.get(2)); + assertEquals("bad removeAll", text3, content.get(3)); + assertEquals("bad removeAll", quux, content.get(4)); + assertEquals("bad removeAll", null, text4.getParent()); + assertEquals("bad removeAll", null, comment.getParent()); + assertEquals("bad removeAll", null, bar.getParent()); + + List children = foo.getChildren(); + List removeList2 = new ArrayList(); + removeList2.add(baz); + removeList2.add(quux); + removeList2.add(new Integer(17)); // should have no effect. + children.removeAll(removeList2); + assertEquals("bad removeAll", 0, children.size()); + assertEquals("bad removeAll", null, baz.getParent()); + assertEquals("bad removeAll", null, quux.getParent()); + } + + @Test + public void test_TCM__boolean_retainAll_Collection() { + List content = foo.getContent(); + List retainList = new ArrayList(); + retainList.add(text3); + retainList.add(quux); + retainList.add(text1); + retainList.add(baz); + retainList.add(text2); + content.retainAll(retainList); + assertEquals("bad retainAll", 5, content.size()); + assertEquals("bad retainAll", text1, content.get(0)); + assertEquals("bad retainAll", text2, content.get(1)); + assertEquals("bad retainAll", baz, content.get(2)); + assertEquals("bad retainAll", text3, content.get(3)); + assertEquals("bad retainAll", quux, content.get(4)); + assertEquals("bad retainAll", null, text4.getParent()); + assertEquals("bad retainAll", null, comment.getParent()); + assertEquals("bad retainAll", null, bar.getParent()); + + List children = foo.getChildren(); + List retainList2 = new ArrayList(); + retainList2.add(baz); + children.retainAll(retainList2); + assertEquals("bad retainAll", 1, children.size()); + assertEquals("bad retainAll", null, quux.getParent()); + } + + @Test + public void test_TCM__List_subList_int_int() { + List children = foo.getChildren(); + List content = foo.getContent(); + + List contentSublist = content.subList(3, 7); // baz, text3, comment, quux + contentSublist.add(comment2); + assertEquals("bad subList", 5, contentSublist.size()); + assertEquals("bad subList", baz, contentSublist.get(0)); + assertEquals("bad subList", text3, contentSublist.get(1)); + assertEquals("bad subList", comment, contentSublist.get(2)); + assertEquals("bad subList", quux, contentSublist.get(3)); + assertEquals("bad subList", comment2, contentSublist.get(4)); + + List childrenSublist = children.subList(0, 2); // bar, baz + childrenSublist.remove(0); + assertEquals("bad subList", 1, childrenSublist.size()); + assertEquals("bad subList", baz, childrenSublist.get(0)); + + assertEquals("wrong element from get", baz, children.get(0)); + assertEquals("wrong element from get", quux, children.get(1)); + + assertEquals("wrong element from get", text1, content.get(0)); + assertEquals("wrong element from get", text2, content.get(1)); + assertEquals("wrong element from get", baz, content.get(2)); + assertEquals("wrong element from get", text3, content.get(3)); + assertEquals("wrong element from get", comment, content.get(4)); + assertEquals("wrong element from get", quux, content.get(5)); + assertEquals("wrong element from get", comment2, content.get(6)); + assertEquals("wrong element from get", text4, content.get(7)); + } + + + // We'll assume that this is a decent test of iterator(), + // listIterator(), and listIterator(int). + @Test + public void test_TCM__ListIterator_listIterator_int() { + List children = foo.getChildren(); + ListIterator iter = children.listIterator(1); + + // next + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertTrue("hasNext is false", iter.hasNext()); + assertEquals("wrong element from get", baz, iter.next()); + assertTrue("hasNext is false", iter.hasNext()); + assertEquals("wrong element from get", quux, iter.next()); + assertFalse("hasNext is true", iter.hasNext()); + + // prev + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertEquals("wrong element from get", quux, iter.previous()); + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertEquals("wrong element from get", baz, iter.previous()); + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertEquals("wrong element from get", bar, iter.previous()); + assertFalse("hasPrevious is true", iter.hasPrevious()); + assertTrue("hasNext is false", iter.hasNext()); + + List content = foo.getContent(); + ListIterator iter2 = content.listIterator(1); + + // next + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", bar, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", text2, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", baz, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", text3, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", comment, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", quux, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", text4, iter2.next()); + assertFalse("hasNext is true", iter2.hasNext()); + + // prev + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", text4, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", quux, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", comment, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", text3, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", baz, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", text2, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", bar, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", text1, iter2.previous()); + assertFalse("hasPrevious is true", iter2.hasPrevious()); + assertTrue("hasNext is false", iter2.hasNext()); + + try { + children.listIterator(48); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + children.listIterator(-3); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.listIterator(48); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + try { + content.listIterator(-3); + fail("Should have thrown an IndexOutOfBoundsException"); + } catch(IndexOutOfBoundsException ex) { + // Do nothing + } catch (Exception e) { + fail("Unexpected exception " + e.getClass()); + } + } + + @Test + public void test_TCM__ListIterator_listIterator_int2() { + List children = foo.getChildren(); + ListIterator iter = children.listIterator(1); + + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertTrue("hasNext is false", iter.hasNext()); + assertTrue("hasNext is false", iter.hasNext()); + assertEquals("wrong element from get", baz, iter.next()); + assertEquals("wrong element from get", baz, iter.previous()); + assertEquals("wrong element from get", baz, iter.next()); + assertEquals("wrong element from get", baz, iter.previous()); + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertTrue("hasPrevious is false", iter.hasPrevious()); + assertEquals("wrong element from get", bar, iter.previous()); + assertEquals("wrong element from get", bar, iter.next()); + assertEquals("wrong element from get", bar, iter.previous()); + assertFalse("hasPrevious is true", iter.hasPrevious()); + assertFalse("hasPrevious is true", iter.hasPrevious()); + assertTrue("hasNext is false", iter.hasNext()); + assertTrue("hasNext is false", iter.hasNext()); + + List content = foo.getContent(); + ListIterator iter2 = content.listIterator(1); + + // next + assertEquals("wrong element from get", bar, iter2.next()); + assertEquals("wrong element from get", text2, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", baz, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", text3, iter2.next()); + assertEquals("wrong element from get", comment, iter2.next()); + assertTrue("hasNext is false", iter2.hasNext()); + assertTrue("hasNext is false", iter2.hasNext()); + assertEquals("wrong element from get", quux, iter2.next()); + assertEquals("wrong element from get", quux, iter2.previous()); + assertEquals("wrong element from get", comment, iter2.previous()); + assertEquals("wrong element from get", text3, iter2.previous()); + assertEquals("wrong element from get", baz, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertTrue("hasPrevious is false", iter2.hasNext()); + assertEquals("wrong element from get", text2, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertTrue("hasPrevious is false", iter2.hasNext()); + assertEquals("wrong element from get", text2, iter2.next()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertTrue("hasPrevious is false", iter2.hasNext()); + assertEquals("wrong element from get", text2, iter2.previous()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertTrue("hasPrevious is false", iter2.hasPrevious()); + assertEquals("wrong element from get", bar, iter2.previous()); + assertEquals("wrong element from get", text1, iter2.previous()); + assertTrue("hasNext is false", iter2.hasNext()); + assertTrue("hasNext is false", iter2.hasNext()); + } + + // When we add the first attribute or child, or when we replace the list of + // attributes or children, we may replace the underlying list reference + // in the Element. We need to make sure that any previously-created FilterLists + // somehow get updated. + @Test + public void test_list_replacement() { + Element el = new Element("parent"); + + List attr = el.getAttributes(); + el.setAttribute("test", "test"); + assertEquals("wrong list size after adding attribute", 1, attr.size()); + ArrayList attr2 = new ArrayList(); + attr2.add(new Attribute("test", "test")); + attr2.add(new Attribute("test2", "test2")); + el.setAttributes(attr2); + assertEquals("wrong list size after replacing attribute", 2, attr.size()); + + List content = el.getContent(); + el.addContent(new Element("test")); + assertEquals("wrong list size after adding content", 1, content.size()); + ArrayList content2 = new ArrayList(); + content2.add(new Element("test")); + content2.add(new Element("test2")); + el.setContent(content2); + content.size(); + assertEquals("wrong list size after replacing content", 2, content.size()); + } + + @Test + public void test_TCM__ListIterator_listIterator_int3() { + try { + Element r = new Element("root"); + new Document().setRootElement(r); + r.addContent(new Element("element").setText("1")); + r.addContent(new Element("element").setText("2")); + r.addContent(new Element("element").setText("3")); + + Element xxx = new Element("element").setText("xxx"); + Element yyy = new Element("element").setText("yyy"); + + ListIterator i = r.getChildren("element").listIterator(); + while (i.hasNext()) { + Element e = i.next(); + i.add(new Element("element").setText(e.getText() + "_x")); + i.add(new Element("element").setText(e.getText() + "_y")); // bug1 - double add should work + } + i.add(xxx); // bug2 - add at end of list.... + assertEquals("previous() is not recent add()", xxx, i.previous()); + i.set(yyy); + assertEquals("yyy not attached", r, yyy.getParent()); + assertFalse("xxx is still attached", xxx.isAncestor(r)); + + i.remove(); + + } catch (OutOfMemoryError oom) { + System.gc(); + oom.printStackTrace(); + fail("ListIterator.add() caused OutOfMemory!"); + } catch (Exception e) { + e.printStackTrace(); + fail("Unable to complete ListIterator tests"); + } + } + + @Test + public void testSpecialCaseLotsOfDataAdded() { + // we need to ensure that the FilterList expands correctly when the + // base list is expanded... + Element root = new Element("root"); + final int size = 40; + final int mid = size / 2; + Element[] kids = new Element[size]; + for (int i = 0; i < mid; i++) { + kids[i] = new Element("kid"); + root.addContent(kids[i]); + } + List kidl = root.getChildren("kid"); + assertTrue(kidl.size() == mid); + for (int i = 0; i < mid; i++) { + assertTrue(kids[i] == kidl.get(i)); + } + + // OK, here is the real test, the FilterList has been set with a base + // size of 'mid', but we now add a bucnh more stuff, we need to make + // sure it stays 'live' appropriately. + for (int i = mid; i < size; i++) { + kids[i] = new Element("kid"); + root.addContent(kids[i]); + } + + // and finally, the test..... make sure the kidl contains all members. + int c = 0; + for (Element k : kidl) { + assertTrue(kids[c++] == k); + } + assertTrue(c == size); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestFilterListElement.java b/test/src/java/org/jdom/test/cases/TestFilterListElement.java new file mode 100644 index 0000000..7feef68 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestFilterListElement.java @@ -0,0 +1,123 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; + +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.test.util.AbstractTestList; + +@SuppressWarnings("javadoc") +public class TestFilterListElement extends AbstractTestList { + + public TestFilterListElement() { + super(Element.class, false); + } + + @Override + public List buildEmptyList() { + Element root = new Element("root"); + return root.getChildren(null, Namespace.getNamespace("kidnamespace")); + } + + @Override + public Element[] buildSampleContent() { + Namespace kns = Namespace.getNamespace("kidnamespace"); + return new Element[] { + new Element("kida", kns), + new Element("kidb", kns), + new Element("kidc", kns), + new Element("kidd", kns), + new Element("kide", kns), + new Element("kidf", kns), + new Element("kidg", kns), + new Element("kidh", kns), + new Element("kidi", kns), + new Element("kidj", kns), + }; + } + + @Override + public Element[] buildAdditionalContent() { + Namespace kns = Namespace.getNamespace("kidnamespace"); + return new Element[]{ new Element("kidk", kns), + new Element("kidl", kns)}; + } + + @Override + public Element[] buildIllegalArgumentContent() { + // illegal for the filter because it has the wrong namespace. + Element kid = new Element("kid"); + return new Element[] { kid }; + } + + @Override + public Object[] buildIllegalClassContent() { + return new Object[] {}; + } + + + /** + * Issue #81 - multiple concurrent 'open' FilterLIsts do not re-sync on remove.... + */ + @Test + public void testMultiLists() { + Element root = new Element("root"); + root.addContent(new Element("A")); + root.addContent(new Element("B")); + root.addContent(new Element("C")); + root.addContent(new Element("A")); + root.addContent(new Element("B")); + root.addContent(new Element("C")); + root.addContent(new Element("A")); + root.addContent(new Element("B")); + root.addContent(new Element("C")); + root.addContent(new Element("A")); + root.addContent(new Element("B")); + root.addContent(new Element("C")); + + List as = root.getChildren("A"); + List bs = root.getChildren("B"); + List cs = root.getChildren("C"); + + for (Element f : as) { + assertTrue("A".equals(f.getName())); + } + for (Element f : bs) { + assertTrue("B".equals(f.getName())); + } + for (Element f : cs) { + assertTrue("C".equals(f.getName())); + } + + final int bsz = bs.size(); + final int csz = cs.size(); + + while (!as.isEmpty()) { + + final int sz = as.size() - 1; + Element e = as.get(0); + as.remove(e); + + assertTrue(sz == as.size()); + assertTrue(bsz == bs.size()); + assertTrue(csz == cs.size()); + + for (Element f : as) { + assertTrue("A".equals(f.getName())); + } + for (Iterator bi = bs.iterator(); bi.hasNext();) { + assertTrue("B".equals(bi.next().getName())); + } + for (Element f : cs) { + assertTrue("C".equals(f.getName())); + } + } + + + } +} diff --git a/test/src/java/org/jdom/test/cases/TestFilterListText.java b/test/src/java/org/jdom/test/cases/TestFilterListText.java new file mode 100644 index 0000000..3b8e52c --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestFilterListText.java @@ -0,0 +1,55 @@ +package org.jdom.test.cases; + +import java.util.List; + +import org.jdom.Element; +import org.jdom.Text; +import org.jdom.filter.Filters; +import org.jdom.test.util.AbstractTestList; + +@SuppressWarnings("javadoc") +public class TestFilterListText extends AbstractTestList { + + public TestFilterListText() { + super(Text.class, false); + } + + @Override + public List buildEmptyList() { + Element root = new Element("root"); + return root.getContent(Filters.text()); + } + + @Override + public Text[] buildSampleContent() { + return new Text[] { + new Text("kida"), + new Text("kidb"), + new Text("kidc"), + new Text("kidd"), + new Text("kide"), + new Text("kidf"), + new Text("kidg"), + new Text("kidh"), + new Text("kidi"), + new Text("kidj"), + }; + } + + @Override + public Text[] buildAdditionalContent() { + return new Text[] { }; + } + + @Override + public Text[] buildIllegalArgumentContent() { + return new Text[] { }; + } + + @Override + public Object[] buildIllegalClassContent() { + return new Object[] {}; + } + + +} diff --git a/test/src/java/org/jdom/test/cases/TestJDOMExceptn.java b/test/src/java/org/jdom/test/cases/TestJDOMExceptn.java new file mode 100644 index 0000000..22481b4 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestJDOMExceptn.java @@ -0,0 +1,73 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.PrintStream; +import java.io.PrintWriter; + +import org.jdom.JDOMException; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestJDOMExceptn { + + @Test + public void testJDOMException() { + JDOMException e = new JDOMException(); + assertEquals(e.getMessage(), "Error occurred in JDOM application."); + assertEquals(e.getCause(), null); + } + + @Test + public void testJDOMExceptionString() { + JDOMException e = new JDOMException("foo"); + assertEquals(e.getMessage(), "foo"); + assertEquals(e.getCause(), null); + } + + @Test + public void testJDOMExceptionStringThrowable() { + Exception c = new RuntimeException("cause"); + JDOMException e = new JDOMException("foo", c); + // assertEquals(e.getMessage(), "foo"); + assertEquals(e.getCause(), c); + } + + @Test + public void testInitCauseThrowable() { + Exception c = new RuntimeException("cause"); + JDOMException e = new JDOMException("foo"); + assertEquals(e.getMessage(), "foo"); + assertEquals(e.getCause(), null); + e.initCause(c); + assertEquals(e.getCause(), c); + } + + @Test + public void testPrintStackTracePrintStream() { + Exception c = new RuntimeException("cause"); + JDOMException e = new JDOMException("foo", c); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + e.printStackTrace(new PrintStream(baos)); + String msg = baos.toString(); + assertTrue(msg != null); + assertTrue(msg.indexOf(JDOMException.class.getName()) == 0); + assertTrue(msg.indexOf("Caused by") > 0); + } + + @Test + public void testPrintStackTracePrintWriter() { + Exception c = new RuntimeException("cause"); + JDOMException e = new JDOMException("foo", c); + CharArrayWriter caw = new CharArrayWriter(); + e.printStackTrace(new PrintWriter(caw)); + String msg = caw.toString(); + assertTrue(msg != null); + assertTrue(msg.indexOf(JDOMException.class.getName()) == 0); + assertTrue(msg.indexOf("Caused by") > 0); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/TestNamespace.java b/test/src/java/org/jdom/test/cases/TestNamespace.java new file mode 100644 index 0000000..16e20ce --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestNamespace.java @@ -0,0 +1,358 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import javax.xml.XMLConstants; + +import org.jdom.*; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import static org.junit.Assert.*; + +@SuppressWarnings("javadoc") +public final class TestNamespace { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestNamespace.class); + } + + /** + * Test the object comparison method. + */ + @Test + public void test_TCM__boolean_equals_Object() { + Namespace ns = Namespace.getNamespace("prefx", "http://some.other.place"); + Object ob = ns; + assertTrue("object not equal to attribute", ns.equals(ob)); + + ns = Namespace.NO_NAMESPACE; + ob = ns; + assertTrue("object not equal to attribute", ns.equals(ob)); + + //ns = Namespace.EMPTY_NAMESPACE; + //ob = (Object)ns; + //assertTrue("object not equal to attribute", ns.equals(ob)); + + + } + + /** + * Verify that a namespace will produce a hashcode. + */ + @Test + public void test_TCM__int_hashCode() { + Namespace ns = Namespace.getNamespace("test", "value"); + //only an exception would be a problem + int i = -1; + try { + i = ns.hashCode(); + } + catch(Exception e) { + fail("bad hashCode"); + } + + //make sure a new one doesn't have the same value + Namespace ns2 = Namespace.getNamespace("test", "value2"); + int x = ns2.hashCode(); + assertTrue("duplicate hashCode", i!=x ); + + //test hashcode for NO_NAMESPACE + //only an exception would be a problem + try { + // Namespace.hashCode() is uri.hashCode(). + // NO_NAMESPACE has URI "" + assertTrue(Namespace.NO_NAMESPACE.hashCode() == "".hashCode()); + } + catch(Exception e) { + fail("bad hashCode"); + } + + //test hashcode for NO_NAMESPACE + //y = Namespace.EMPTY_NAMESPACE.hashCode(); + //only an exception would be a problem + //assertTrue("bad hashcode" , true); + } + + /** + * Test the URI only Namespace. + */ + @Test + public void test_TCM__OrgJdomNamespace_getNamespace_String() { + Namespace ns = Namespace.getNamespace("http://some.new.place"); + assertTrue("Incorrect namespace created", ns.toString().equals("[Namespace: prefix \"\" is mapped to URI \"http://some.new.place\"]")); + //the is really the default NO_NAMESPACE version + Namespace ns2 = Namespace.getNamespace(""); + assertTrue("Incorrect no namespace namespace created", ns2.toString().equals("[Namespace: prefix \"\" is mapped to URI \"\"]")); + + } + + /** + * Test the prefix, uri version of getNamespace. + */ + @Test + public void test_TCM__OrgJdomNamespace_getNamespace_String_String() { + Namespace ns = Namespace.getNamespace("prefx", "http://some.other.place"); + assertTrue("Incorrect namespace created", ns.toString().equals("[Namespace: prefix \"prefx\" is mapped to URI \"http://some.other.place\"]")); + + assertTrue(Namespace.NO_NAMESPACE == Namespace.getNamespace("")); + assertTrue(Namespace.NO_NAMESPACE == Namespace.getNamespace(null)); + assertTrue(Namespace.NO_NAMESPACE == Namespace.getNamespace(null, null)); + assertTrue(Namespace.NO_NAMESPACE == Namespace.getNamespace(null, "")); + assertTrue(Namespace.NO_NAMESPACE == Namespace.getNamespace("", "")); + assertTrue(Namespace.NO_NAMESPACE == Namespace.getNamespace("", null)); + + assertTrue(Namespace.XML_NAMESPACE == Namespace.getNamespace( + XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI)); + + try { + assertTrue(null != Namespace.getNamespace("xml", "myuri")); + fail("Should not be able to re-brand Namespace prefix 'xml'"); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("expected IllegalNameException, not " + e.getClass().getName()); + } + + try { + assertTrue(null != Namespace.getNamespace("pfx", XMLConstants.XML_NS_URI)); + fail("Should not be able to re-brand xml namespace URI to another prefix too."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("expected IllegalNameException, not " + e.getClass().getName()); + } + + try { + assertTrue(null != Namespace.getNamespace("pfx", "-this is illegal...")); + fail("Should not be able to have a URI starting with '-'."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("expected IllegalNameException, not " + e.getClass().getName()); + } + + try { + assertTrue(null != Namespace.getNamespace("p:x", "myuri")); + fail("Should not be able to create Namespace with illegal prefix 'p:x'"); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("expected IllegalNameException, not " + e.getClass().getName()); + } + + try { + assertTrue(null != Namespace.getNamespace("pfx", " ")); + fail("Should not be able to create Namespace with no URI"); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("expected IllegalNameException, not " + e.getClass().getName()); + } + + try { + assertTrue(null != Namespace.getNamespace("pfx", null)); + fail("Should not be able to create Namespace with no URI"); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + fail("expected IllegalNameException, not " + e.getClass().getName()); + } + + } + + /** + * Test getPrefix() + */ + @Test + public void test_TCM__String_getPrefix() { + Namespace ns = Namespace.getNamespace("prefx","http://foo"); + assertTrue("Incorrect namespace prefix", ns.getPrefix().equals("prefx")); + + //ns = Namespace.EMPTY_NAMESPACE; + //assertTrue("Incorrect empty namespace prefix", ns.getPrefix().equals("")); + + ns = Namespace.NO_NAMESPACE; + assertTrue("Incorrect empty namespace prefix", ns.getPrefix().equals("")); + + } + + /** + * Test than a namespace returns the correct URI + */ + @Test + public void test_TCM__String_getURI() { + Namespace ns = Namespace.getNamespace("prefx","http://foo"); + assertTrue("Incorrect namespace prefix", ns.getURI().equals("http://foo")); + + } + + /** + * Test that toString() operates according to JDOM specs + */ + @Test + public void test_TCM__String_toString() { + Namespace ns = Namespace.getNamespace("http://some.new.place"); + assertTrue("Incorrect namespace created", ns.toString().equals("[Namespace: prefix \"\" is mapped to URI \"http://some.new.place\"]")); + //the is really the default NO_NAMESPACE version + Namespace ns2 = Namespace.getNamespace(""); + assertTrue("Incorrect no namespace namespace created", ns2.toString().equals("[Namespace: prefix \"\" is mapped to URI \"\"]")); + ns2 = Namespace.getNamespace("prefx","http://foo"); + assertTrue("Incorrect namespace created", ns2.toString().equals("[Namespace: prefix \"prefx\" is mapped to URI \"http://foo\"]")); + + } + + @Test + public void testXMLNamespaceGood() { + Namespace ns = Namespace.getNamespace("xml", "http://www.w3.org/XML/1998/namespace"); + assertTrue(ns == Namespace.XML_NAMESPACE); + } + + @Test + public void testXMLNamespacePrefix() { + try { + Namespace.getNamespace("xml", "not right"); + fail("Should not be able to redefine 'xml' prefix."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("We expect IllegalNameException not " + e.getClass()); + } + } + + @Test + public void testXMLNamespaceOnlyPrefix() { + try { + Namespace.getNamespace("other", JDOMConstants.NS_URI_XML); + fail("Should not be able to have XML Namespace URI mapped to other prefix."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("We expect IllegalNameException not " + e.getClass()); + } + } + + @Test + public void testXMLNamespaceDefault() { + try { + Namespace.getNamespace(JDOMConstants.NS_URI_XML); + fail("Should not be able to have XML Namespace URI mapped the default namespace."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("We expect IllegalNameException not " + e.getClass()); + } + } + + + @Test + public void testXMLNSNamespaceGood() { + Namespace ns = Namespace.getNamespace("xmlns", "http://www.w3.org/2000/xmlns/"); + assertTrue("xmlns".equals(ns.getPrefix())); + } + + @Test + public void testXMLNSNamespacePrefix() { + try { + Namespace.getNamespace("xmlns", "not right"); + fail("Should not be able to redefine 'xmlns' prefix."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("We expect IllegalNameException not " + e.getClass()); + } + } + + @Test + public void testXMLNSNamespaceOnlyPrefix() { + try { + Namespace.getNamespace("other", JDOMConstants.NS_URI_XMLNS); + fail("Should not be able to have XMLNS Namespace URI mapped to other prefix."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("We expect IllegalNameException not " + e.getClass()); + } + } + + @Test + public void testXMLNSNamespaceDefault() { + try { + Namespace.getNamespace(JDOMConstants.NS_URI_XMLNS); + fail("Should not be able to have XMLNS Namespace URI mapped the default namespace."); + } catch (IllegalNameException ine) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("We expect IllegalNameException not " + e.getClass()); + } + } + + +} diff --git a/test/src/java/org/jdom/test/cases/TestNamespaceAware.java b/test/src/java/org/jdom/test/cases/TestNamespaceAware.java new file mode 100644 index 0000000..d505857 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestNamespaceAware.java @@ -0,0 +1,170 @@ +package org.jdom.test.cases; + +import org.jdom.Attribute; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.Text; +import org.jdom.test.util.UnitTestUtil; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestNamespaceAware { + + @Test + public void testNamespacesScopeSimple() { + Element emt = new Element("root"); + + UnitTestUtil.testNamespaceIntro(emt); + UnitTestUtil.testNamespaceScope(emt, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesAttributeDetach() { + Attribute att = new Attribute("att", "value"); + + UnitTestUtil.testNamespaceIntro(att, Namespace.NO_NAMESPACE); + UnitTestUtil.testNamespaceScope(att, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesAttributeDetachNS() { + Namespace ns = Namespace.getNamespace("pfx", "nspfx"); + Attribute att = new Attribute("att", "value", ns); + + UnitTestUtil.testNamespaceIntro(att, ns); + UnitTestUtil.testNamespaceScope(att, ns, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesAttributeDetachZZZ() { + // order should not change with zzz prefix + Namespace ns = Namespace.getNamespace("zzz", "nspfx"); + Attribute att = new Attribute("att", "value", ns); + + UnitTestUtil.testNamespaceIntro(att, ns); + UnitTestUtil.testNamespaceScope(att, ns, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesText() { + // order should not change with zzz prefix + Text txt = new Text("txt"); + + UnitTestUtil.testNamespaceIntro(txt); + UnitTestUtil.testNamespaceScope(txt, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesScopeSimpleAdded() { + Element emt = new Element("root"); + Namespace pfx = Namespace.getNamespace("pfx", "nsuri"); + emt.addNamespaceDeclaration(pfx); + + UnitTestUtil.testNamespaceIntro(emt, pfx); + UnitTestUtil.testNamespaceScope(emt, Namespace.NO_NAMESPACE, pfx, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesScopeSimpleElement() { + Namespace pfx = Namespace.getNamespace("pfx", "nsuri"); + Element emt = new Element("root", pfx); + + // just to mix it up, double-up the declaration. + // cover a condition in getNamespacesInScope(); + emt.addNamespaceDeclaration(pfx); + + UnitTestUtil.testNamespaceIntro(emt, pfx); + UnitTestUtil.testNamespaceScope(emt, pfx, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesScopeDeepElement() { + Namespace pfx = Namespace.getNamespace("pfx", "nsuri"); + Namespace pfy = Namespace.getNamespace("pfy", "nsyyy"); + Element emt = new Element("root", pfx); + Element kid = new Element("kid", pfy); + emt.addContent(kid); + + UnitTestUtil.testNamespaceIntro(emt, pfx); + UnitTestUtil.testNamespaceScope(emt, pfx, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + UnitTestUtil.testNamespaceIntro(kid, pfy); + UnitTestUtil.testNamespaceScope(kid, pfy, Namespace.NO_NAMESPACE, pfx, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesScopeSimpleAttribute() { + Namespace pfx = Namespace.getNamespace("pfx", "nsuri"); + Element emt = new Element("root"); + Attribute att = new Attribute("att", "val", pfx); + emt.setAttribute(att); + + UnitTestUtil.testNamespaceIntro(emt, pfx); + UnitTestUtil.testNamespaceScope(emt, Namespace.NO_NAMESPACE, pfx, Namespace.XML_NAMESPACE); + + UnitTestUtil.testNamespaceIntro(att); + UnitTestUtil.testNamespaceScope(att, pfx, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + } + + + @Test + public void testNamespacesScopeSimpleAttributeNoNs() { + Namespace pfx = Namespace.getNamespace("pfx", "nsuri"); + Element emt = new Element("root", pfx); + Attribute att = new Attribute("att", "val"); + emt.setAttribute(att); + + UnitTestUtil.testNamespaceIntro(emt, pfx); + UnitTestUtil.testNamespaceScope(emt, pfx, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + UnitTestUtil.testNamespaceIntro(att); + UnitTestUtil.testNamespaceScope(att, Namespace.NO_NAMESPACE, pfx, Namespace.XML_NAMESPACE); + } + + @Test + public void testNamespacesScopeDocument() { + Element emt = new Element("root"); + Document doc = new Document(emt); + + UnitTestUtil.testNamespaceIntro(doc, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + UnitTestUtil.testNamespaceScope(doc, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + } + + @Test + public void testNamespacesScopeTreeAttributeNoNs() { + Namespace pfx = Namespace.getNamespace("pfx", "nsuri"); + Element emt = new Element("root", pfx); + // note that 'kid' is in the NO_NAMESPACE namespace. + Element kid = new Element("kid"); + Text txt = new Text("txt"); + kid.addContent(txt); + emt.addContent(kid); + emt.setAttribute("att", "val"); + + Namespace kfx = Namespace.getNamespace("kfx", "nskid"); + kid.addNamespaceDeclaration(kfx); + + UnitTestUtil.testNamespaceIntro(emt, pfx); + UnitTestUtil.testNamespaceScope(emt, pfx, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + + + UnitTestUtil.testNamespaceIntro(kid, kfx); + UnitTestUtil.testNamespaceScope(kid, Namespace.NO_NAMESPACE, kfx, pfx, Namespace.XML_NAMESPACE); + + UnitTestUtil.testNamespaceIntro(txt); + UnitTestUtil.testNamespaceScope(txt, Namespace.NO_NAMESPACE, kfx, pfx, Namespace.XML_NAMESPACE); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestProcessingInstruction.java b/test/src/java/org/jdom/test/cases/TestProcessingInstruction.java new file mode 100644 index 0000000..c914ede --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestProcessingInstruction.java @@ -0,0 +1,350 @@ +package org.jdom.test.cases; + +import static org.jdom.test.util.UnitTestUtil.checkEquals; +import static org.jdom.test.util.UnitTestUtil.cloneString; +import static org.jdom.test.util.UnitTestUtil.deSerialize; +import static org.junit.Assert.*; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.jdom.Element; +import org.jdom.IllegalDataException; +import org.jdom.IllegalNameException; +import org.jdom.IllegalTargetException; +import org.jdom.ProcessingInstruction; +import org.junit.Ignore; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestProcessingInstruction { + + @Test + public void testProcessingInstruction() { + ProcessingInstruction pi = new ProcessingInstruction() { + // nothing + private static final long serialVersionUID = 200L; + }; + assertTrue(null == pi.getTarget()); + assertTrue(null == pi.getValue()); + } + + @Test + public void testProcessingInstructionString() { + ProcessingInstruction pi = new ProcessingInstruction("test"); + checkEquals(pi.getTarget(), "test"); + checkEquals(pi.getValue(), ""); + } + + @Test + public void testProcessingInstructionStringString() { + ProcessingInstruction pi = new ProcessingInstruction("test", "key='value'"); + checkEquals(pi.getTarget(), "test"); + checkEquals(pi.getValue(), "key='value'"); + } + + @Test + public void testProcessingInstructionStringMap() { + Mapkvs = buildMap( + "key1", "val1", + "key2", "val2" + ); + ProcessingInstruction pi = new ProcessingInstruction("test", kvs); + checkEquals(pi.getTarget(), "test"); + assertTrue(pi.getValue() != null); + checkMapValues(pi, kvs); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentA() { + new ProcessingInstruction("1tgt", "data"); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentB() { + new ProcessingInstruction(" tgt", "data"); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentC() { + new ProcessingInstruction("tg?>t", "data"); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentD() { + new ProcessingInstruction("tgt ", "data"); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentE() { + new ProcessingInstruction("t gt", "data"); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentF() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", "data"); + pi.setTarget("tg t"); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentG() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", "data"); + pi.setTarget("tgt "); + } + + @Test (expected=IllegalTargetException.class) + public void testIllegalTargetContentH() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", "data"); + pi.setTarget("tg?t"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalAttnameA() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", "data"); + pi.setPseudoAttribute("tg?>t", "val"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalAttnameB() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", buildMap("ok", "val")); + pi.setPseudoAttribute("b?>a", "val"); + } + + @Test (expected=IllegalNameException.class) + @Ignore // FIXME + public void testIllegalAttnameC() { + new ProcessingInstruction("tgt", buildMap("ok", "val", "b a", "val")); + } + + @Test (expected=IllegalDataException.class) + @Ignore //FIXME + public void testIllegalAttValueA() { + new ProcessingInstruction("tgt", buildMap("ok", "val", " bad", "va'''\"\"\"l")); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalAttValueB() { + new ProcessingInstruction("tgt", buildMap("ok", "val", "b a", "v?>al")); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalAttValueC() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", buildMap("ok", "val")); + pi.setPseudoAttribute("b a", "v?>al"); + } + + @Test (expected=IllegalDataException.class) + public void testIllegalData() { + ProcessingInstruction pi = new ProcessingInstruction("tgt", buildMap("ok", "val")); + pi.setData("b?>a=val"); + } + + private void checkMapValues(ProcessingInstruction pi, Map vals) { + for (Map.Entry me : vals.entrySet()) { + checkEquals(pi.getPseudoAttributeValue(me.getKey()), me.getValue()); + } + for (Object s : pi.getPseudoAttributeNames() ) { + if (!vals.containsKey(s)) { + fail("ProcessingInstruction has key " + s + " which is not expected."); + } + } + } + + private Map buildMap(String...kvpairs) { + assertTrue(kvpairs != null); + assertTrue(kvpairs.length % 2 == 0); + LinkedHashMap lhm = new LinkedHashMap(); + for (int i = 0; i < kvpairs.length; i += 2) { + lhm.put(kvpairs[i], kvpairs[i+1]); + } + return lhm; + } + + @Test + public void testGetValue() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + checkEquals(cloneString("value"), pi.getValue()); + } + + @Test + public void testCloneA() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + ProcessingInstruction copy = pi.clone(); + assertTrue(!pi.equals(copy)); + checkEquals(pi.getTarget(), copy.getTarget()); + checkEquals(pi.getValue(), copy.getValue()); + } + + @Test + public void testCloneB() { + ProcessingInstruction pi = new ProcessingInstruction("test", ""); + ProcessingInstruction copy = pi.clone(); + assertTrue(!pi.equals(copy)); + checkEquals(pi.getTarget(), copy.getTarget()); + checkEquals(pi.getValue(), copy.getValue()); + } + + @Test + public void testCloneC() { + ProcessingInstruction pi = new ProcessingInstruction("test", ""); + pi.setPseudoAttribute("hi", "val"); + pi.removePseudoAttribute("hi"); + ProcessingInstruction copy = pi.clone(); + assertTrue(!pi.equals(copy)); + checkEquals(pi.getTarget(), copy.getTarget()); + checkEquals(pi.getValue(), copy.getValue()); + } + + @Test + public void testCloneD() { + Map empty = Collections.emptyMap(); + ProcessingInstruction pi = new ProcessingInstruction("test", empty); + ProcessingInstruction copy = pi.clone(); + assertTrue(!pi.equals(copy)); + checkEquals(pi.getTarget(), copy.getTarget()); + checkEquals(pi.getValue(), copy.getValue()); + } + + @Test + public void testSerialize() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + ProcessingInstruction copy = deSerialize(pi); + checkEquals(pi.getTarget(), copy.getTarget()); + checkEquals(pi.getValue(), copy.getValue()); + } + + @Test + public void testSetTarget() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + checkEquals(pi.getTarget(), "test"); + checkEquals(pi.getValue(), "value"); + assertTrue(pi == pi.setTarget("test2")); + checkEquals(pi.getTarget(), "test2"); + assertTrue(pi == pi.setTarget("test")); + checkEquals(pi.getTarget(), "test"); + } + + @Test + public void testGetTarget() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + checkEquals(pi.getTarget(), "test"); + checkEquals(pi.getValue(), "value"); + pi.setTarget("test2"); + checkEquals(pi.getTarget(), "test2"); + pi.setTarget("test"); + checkEquals(pi.getTarget(), "test"); + } + + @Test + public void testGetData() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + checkEquals(pi.getTarget(), "test"); + checkEquals(pi.getValue(), "value"); + checkEquals(pi.getData(), "value"); + } + + @Test + public void testToString() { + ProcessingInstruction pi = new ProcessingInstruction("test", "value"); + assertTrue(pi.toString() != null); + } + + @Test + public void testExercise() { + Map data = new LinkedHashMap(); + for (int i = 0; i < 10; i++) { + data.put(String.format("key%02d", i), String.format(" val %2d ", i)); + Map td = new LinkedHashMap(); + td.putAll(data); + exercise(td); + } + + } + + + private void exercise(Map data) { + exerciseQuotes("'", "'", data); + exerciseQuotes("\"", "\"", data); + exerciseQuotes(" \"", "\" ", data); + exerciseQuotes(" '", "' ", data); + exerciseQuotes(" '", "' ", data); + data.put("quote", "Foo'd up!"); + exerciseQuotes("\"", "\"", data); + data.put("quote", "This is a \"quoted\" value."); + exerciseQuotes(" '", "' ", data); + data.remove("quote"); + ProcessingInstruction pi = new ProcessingInstruction("tgt", data); + for (Map.Entry me : data.entrySet()) { + assertEquals(me.getValue(), pi.getPseudoAttributeValue(me.getKey())); + assertTrue(pi.removePseudoAttribute(me.getKey())); + assertFalse(pi.removePseudoAttribute(me.getKey())); + } + for (Map.Entry me : data.entrySet()) { + assertTrue(null == pi.getPseudoAttributeValue(me.getKey())); + assertFalse(pi.removePseudoAttribute(me.getKey())); + assertTrue(pi == pi.setPseudoAttribute(me.getKey(), me.getValue())); + assertEquals(me.getValue(), pi.getPseudoAttributeValue(me.getKey())); + } + checkEquals(mapValue(data), pi.getData()); + + } + + private void exerciseQuotes(String open, String close, + Map data) { + StringBuilder sb = new StringBuilder(); + int cnt = 0; + for(Map.Entry me : data.entrySet()) { + if (cnt++ > 0) { + sb.append(" "); + } + sb.append(me.getKey()).append("=").append(open).append(me.getValue()).append(close); + } + ProcessingInstruction pi = new ProcessingInstruction("test", sb.toString()); + checkEquals(pi.getData(), sb.toString()); + List keys = pi.getPseudoAttributeNames(); + assertTrue(keys != null); + assertTrue(keys.size() == data.size()); + assertTrue(keys.containsAll(data.keySet())); + assertTrue(data.keySet().containsAll(keys)); + for (Map.Entry me : data.entrySet()) { + String val = pi.getPseudoAttributeValue(me.getKey()); + assertTrue(val != null); + String x = me.getValue(); + if (!x.equals(val)) { + fail("We expected value '" + x + "' but got '" + val + "'."); + } + } + assertEquals(pi.getData(), pi.getValue()); + // ProcessingInstruction does not change the quoting unless you mess with + // attribute values. + assertEquals(pi.getData(), pi.getValue()); + pi.setPseudoAttribute("foo", "bar"); + pi.removePseudoAttribute("foo"); + checkEquals(pi.getData(), mapValue(data)); + } + + private String mapValue(Map data) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry me : data.entrySet()) { + if (sb.length() > 0) { + sb.append(" "); + } + sb.append(me.getKey()).append("=\"").append(me.getValue()).append("\""); + } + return sb.toString(); + } + + @Test + public void testCloneDetatchParentProcessingInstruction() { + Element parent = new Element("root"); + ProcessingInstruction content = new ProcessingInstruction("val", ""); + parent.addContent(content); + ProcessingInstruction clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestSerialization.java b/test/src/java/org/jdom/test/cases/TestSerialization.java new file mode 100644 index 0000000..8a238fd --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestSerialization.java @@ -0,0 +1,173 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2007 Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import static org.jdom.test.util.UnitTestUtil.compare; +import static org.jdom.test.util.UnitTestUtil.deSerialize; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Iterator; + +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter2.ElementFilter; + +@SuppressWarnings("javadoc") +public final class TestSerialization { + + /** + * The main method runs all the tests in the text ui + */ + public static void main(String args[]) { + JUnitCore.runClasses(TestSerialization.class); + } + + + private void outAndBack(ElementFilter filter) { + ElementFilter filter2 = deSerialize(filter); + assertTrue(filter.equals(filter2)); + } + + @Test + public void test_ElementFilterName() { + outAndBack(new ElementFilter("name")); + } + + @Test + public void test_ElementFilterNameNamespace() { + outAndBack(new ElementFilter("name", Namespace.XML_NAMESPACE)); + } + + @Test + public void test_ElementFilterNamespace() { + outAndBack(new ElementFilter(Namespace.XML_NAMESPACE)); + } + + @Test + public void test_ElementFilterEmpty() { + outAndBack(new ElementFilter()); + } + + + @Test + public void testDocumentSerialization() { + // test serialization of all JDOM core types. + // Document, Element, DocType, Comment, CDATA, Text, + // ProcessingInstruction, EntityRef, Namespace, and Attribute. + // Additionally, test ContentList and AttributeList + Document doc = new Document(); + doc.setDocType(new DocType("root", "pubid", "sysid")); + doc.getDocType().setInternalSubset(" internalss with space "); + doc.addContent(new Comment("doccomment1")); + doc.addContent(new ProcessingInstruction("target1")); + doc.addContent(new ProcessingInstruction("target2").setData("key=value")); + doc.addContent(new Comment("doccomment2")); + Element root = new Element("root"); + doc.setRootElement(root); + root.setAttribute(new Attribute("att", "value")); + root.addContent(new Text(" ")); + root.addNamespaceDeclaration(Namespace.getNamespace("pfx", "uriupfx")); + root.addContent(new Element("child", Namespace.getNamespace("nopfxuri"))); + root.addContent(new EntityRef("name")); + root.addContent(new CDATA("cdata")); + + + Document ser = deSerialize(doc); + Iterator sit = ser.getDescendants(); + Iterator dit = doc.getDescendants(); + while (sit.hasNext() && dit.hasNext()) { + compare(sit.next(), dit.next()); + } + assertFalse(sit.hasNext()); + assertFalse(dit.hasNext()); + + } + + @Test + public void testAttribute() { + Element root = new Element("root"); + Attribute att = new Attribute( + "name", "value", Namespace.getNamespace("ans", "attnamespace")); + root.setAttribute(att); + + Attribute attc = deSerialize(att); + + assertTrue(attc.getParent() == null); + + compare(att, attc); + } + + + +} diff --git a/test/src/java/org/jdom/test/cases/TestSlimJDOMFactory.java b/test/src/java/org/jdom/test/cases/TestSlimJDOMFactory.java new file mode 100644 index 0000000..7731436 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestSlimJDOMFactory.java @@ -0,0 +1,47 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.jdom.JDOMFactory; +import org.jdom.SlimJDOMFactory; +import org.jdom.Text; + +@SuppressWarnings("javadoc") +public class TestSlimJDOMFactory extends AbstractTestJDOMFactory { + + /** + * @param located + */ + public TestSlimJDOMFactory() { + super(false); + } + + @Override + protected JDOMFactory buildFactory() { + return new SlimJDOMFactory(); + } + + @Test + public void testCaching() { + SlimJDOMFactory fac = new SlimJDOMFactory(); + Text ta = fac.text("hi"); + String hi = ta.getText(); + // we expect the StringBin to compact a string value... should no longer + // be the intern value. + assertTrue("hi" != hi); + assertTrue("hi" == hi.intern()); + + Text tb = fac.text("hi"); + assertTrue("hi" != tb.getText()); + assertTrue(hi == tb.getText()); + + fac.clearCache(); + + Text tc = fac.text("hi"); + assertTrue("hi" != tc.getText()); + assertTrue(hi != tc.getText()); + + assertTrue(hi.equals(tc.getText())); + } +} diff --git a/test/src/java/org/jdom/test/cases/TestSlimJDOMFactoryNoText.java b/test/src/java/org/jdom/test/cases/TestSlimJDOMFactoryNoText.java new file mode 100644 index 0000000..988cb51 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestSlimJDOMFactoryNoText.java @@ -0,0 +1,47 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.*; +import org.junit.Test; + +import org.jdom.JDOMFactory; +import org.jdom.SlimJDOMFactory; +import org.jdom.Text; + +@SuppressWarnings("javadoc") +public class TestSlimJDOMFactoryNoText extends AbstractTestJDOMFactory { + + /** + * @param located + */ + public TestSlimJDOMFactoryNoText() { + super(false); + } + + @Override + protected JDOMFactory buildFactory() { + return new SlimJDOMFactory(false); + } + + @Test + public void testCaching() { + SlimJDOMFactory fac = new SlimJDOMFactory(false); + Text ta = fac.text("hi"); + String hi = ta.getText(); + // we expect the StringBin to compact a string value... should no longer + // be the intern value. + assertTrue("hi" == hi); + assertTrue("hi" == hi.intern()); + + Text tb = fac.text("hi"); + assertTrue("hi" == tb.getText()); + assertTrue(hi == tb.getText()); + + fac.clearCache(); + + Text tc = fac.text("hi"); + assertTrue("hi" == tc.getText()); + assertTrue(hi == tc.getText()); + + assertTrue(hi.equals(tc.getText())); + } +} diff --git a/test/src/java/org/jdom/test/cases/TestText.java b/test/src/java/org/jdom/test/cases/TestText.java new file mode 100644 index 0000000..3da90d7 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestText.java @@ -0,0 +1,123 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import org.jdom.Content; +import org.jdom.Element; +import org.jdom.IllegalDataException; +import org.jdom.Text; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestText { + + @Test + public void testText() { + Text txt = new Text() { + // nothing + private static final long serialVersionUID = 200L; + }; + assertTrue(txt.getValue() == null); + assertTrue(txt.getText() == null); + } + + @Test + public void testTextString() { + Text txt = new Text(" frodo baggins "); + assertTrue(" frodo baggins ".equals(txt.getValue())); + assertTrue(" frodo baggins ".equals(txt.getText())); + assertTrue("frodo baggins".equals(txt.getTextNormalize())); + assertTrue("frodo baggins".equals(txt.getTextTrim())); + } + + @Test + public void testNormalizeString() { + assertEquals("", Text.normalizeString(null)); + assertEquals("", Text.normalizeString("")); + assertEquals("boo", Text.normalizeString("boo")); + assertEquals("boo", Text.normalizeString(" boo")); + assertEquals("boo", Text.normalizeString("boo ")); + assertEquals("boo", Text.normalizeString(" boo ")); + assertEquals("boo hoo", Text.normalizeString("boo hoo")); + assertEquals("boo hoo", Text.normalizeString("boo\nhoo")); + assertEquals("boo hoo", Text.normalizeString("boo \n hoo")); + assertEquals("boo hoo", Text.normalizeString("boo \n hoo")); + assertEquals("boo hoo", Text.normalizeString("\rboo\thoo\n")); + } + + @Test + public void testClone() { + Text txt = new Text("frodo baggins"); + Text clone = txt.clone(); + assertTrue(clone != null); + assertTrue(txt != clone); + assertFalse(txt.equals(clone)); + assertFalse(clone.equals(txt)); + assertTrue("frodo baggins".equals(clone.getText())); + } + + @Test + public void testSetText() { + Text txt = new Text("frodo baggins"); + assertTrue("frodo baggins".equals(txt.getText())); + assertTrue(txt.setText("bilbo baggins") == txt); + assertTrue("bilbo baggins".equals(txt.getText())); + } + + @Test + public void testAppendString() { + Text txt = new Text("frodo baggins"); + assertTrue("frodo baggins".equals(txt.getText())); + txt.append(" from the shire"); + assertTrue("frodo baggins from the shire".equals(txt.getText())); + String app = null; + txt.append(app); + assertTrue("frodo baggins from the shire".equals(txt.getText())); + txt.append(""); + assertTrue("frodo baggins from the shire".equals(txt.getText())); + try { + txt.append("New char data " + (char)0x05 + " with bad characters."); + } catch (IllegalDataException iae) { + // good + } catch (Exception e) { + fail ("Expected IllegalAddException, but got " + e.getClass().getName()); + } + } + + @Test + public void testAppendText() { + Text txt = new Text("frodo baggins"); + assertTrue("frodo baggins".equals(txt.getText())); + txt.append(new Text(" from the shire")); + assertTrue("frodo baggins from the shire".equals(txt.getText())); + Text app = null; + txt.append(app); + assertTrue("frodo baggins from the shire".equals(txt.getText())); + } + + @Test + public void testToString() { + Text txt = new Text("frodo baggins"); + assertTrue(txt.toString() != null); + } + + @Test + public void testCloneDetatchParentText() { + Element parent = new Element("root"); + Text content = new Text("val"); + parent.addContent(content); + Text clone = content.detach().clone(); + assertEquals(content.getValue(), clone.getValue()); + assertNull(content.getParent()); + assertNull(clone.getParent()); + } + + @Test + public void testContentCType() { + assertTrue(Content.CType.Text == new Text("").getCType()); + } +} diff --git a/test/src/java/org/jdom/test/cases/TestUncheckedJDOMFactory.java b/test/src/java/org/jdom/test/cases/TestUncheckedJDOMFactory.java new file mode 100644 index 0000000..c53f7bf --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestUncheckedJDOMFactory.java @@ -0,0 +1,21 @@ +package org.jdom.test.cases; + +import org.jdom.JDOMFactory; +import org.jdom.UncheckedJDOMFactory; + +@SuppressWarnings("javadoc") +public class TestUncheckedJDOMFactory extends AbstractTestJDOMFactory { + + /** + * @param located + */ + public TestUncheckedJDOMFactory() { + super(false); + } + + @Override + protected JDOMFactory buildFactory() { + return new UncheckedJDOMFactory(); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestVerifier.java b/test/src/java/org/jdom/test/cases/TestVerifier.java new file mode 100644 index 0000000..aea1883 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestVerifier.java @@ -0,0 +1,653 @@ +package org.jdom.test.cases; + +/*-- + + Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.Verifier; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +@SuppressWarnings("javadoc") +public final class TestVerifier { + + private final char BADCHAR = (char)0x0b; + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestVerifier.class); + } + + /** + * Test checkElementName such that a name is validated as an + * xml name with the following caveats. + * The name must not start with "-" or ":". + */ + @Test + public void testCheckElementName() { + //check out of range values + assertNotNull("validated invalid null", Verifier.checkElementName(null)); + assertNotNull("validated invalid name with null char", Verifier.checkElementName("test" + (char)0x0)); + assertNotNull("validated invalid name with null char", Verifier.checkElementName("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid name with null char", Verifier.checkElementName((char)0x0 + "test")); + assertNotNull("validated invalid name with 0x01", Verifier.checkElementName((char)0x01 + "test")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkElementName("test" + (char)0xD800)); + assertNotNull("validated invalid name with 0xD800", Verifier.checkElementName("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkElementName((char)0xD800 + "test")); + assertNotNull("validated invalid name with :", Verifier.checkElementName("test" + ':' + "local")); + assertNotNull("validated invalid name with :", Verifier.checkElementName("abcd:")); + assertNotNull("validated invalid name with :", Verifier.checkElementName("abc:d")); + assertNotNull("validated invalid name with :", Verifier.checkElementName("ab:cd")); + assertNotNull("validated invalid name with :", Verifier.checkElementName("a:bcd")); + assertNotNull("validated invalid name with :", Verifier.checkElementName(":abcd")); + + //invalid start characters + assertNotNull("validated invalid name with startin -", Verifier.checkElementName('-' + "test")); + assertNotNull("validated invalid name with startin :", Verifier.checkElementName(':' + "test")); + + //valid tests + assertNull("invalidated valid name with starting _", Verifier.checkElementName('_' + "test")); + assertNull("invalidated valid name with _", Verifier.checkElementName("test" + '_')); + assertNull("invalidated valid name with .", Verifier.checkElementName("test" + '.' + "name")); + assertNull("invalidated valid name with 0x00B7", Verifier.checkElementName("test" + (char)0x00B7)); + assertNull("invalidated valid name with 0x4E01", Verifier.checkElementName("test" + (char)0x4E01)); + assertNull("invalidated valid name with 0x0301", Verifier.checkElementName("test" + (char)0x0301)); + + } + + /** + * Test for a valid Attribute name. A valid Attribute name is + * an xml name (xml start character + xml name characters) with + * some special considerations. "xml:lang" and "xml:space" are + * allowed. No ':' are allowed since prefixes are defined with + * Namespace objects. The name must not be "xmlns" + */ + @Test + public void testCheckAttributeName() { + //check out of range values + assertNotNull("validated invalid null", Verifier.checkAttributeName(null)); + assertNotNull("validated invalid name with null", Verifier.checkAttributeName("test" + (char)0x0)); + assertNotNull("validated invalid name with null", Verifier.checkAttributeName("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid name with null", Verifier.checkAttributeName((char)0x0 + "test")); + assertNotNull("validated invalid name with 0x01", Verifier.checkAttributeName((char)0x01 + "test")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkAttributeName("test" + (char)0xD800)); + assertNotNull("validated invalid name with 0xD800", Verifier.checkAttributeName("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkAttributeName((char)0xD800 + "test")); + assertNotNull("validated invalid name with :", Verifier.checkAttributeName("test" + ':' + "local")); + assertNotNull("validated invalid name with xml:lang", Verifier.checkAttributeName("xml:lang")); + assertNotNull("validated invalid name with xml:space", Verifier.checkAttributeName("xml:space")); + + //invalid start characters + assertNotNull("validated invalid name with startin -", Verifier.checkAttributeName('-' + "test")); + assertNotNull("validated invalid name with xmlns", Verifier.checkAttributeName("xmlns")); + assertNotNull("validated invalid name with startin :", Verifier.checkAttributeName(':' + "test")); + + //valid tests + assertNull("invalidated valid name with starting _", Verifier.checkAttributeName('_' + "test")); + assertNull("invalidated valid name with _", Verifier.checkAttributeName("test" + '_')); + assertNull("invalidated valid name with .", Verifier.checkAttributeName("test" + '.' + "name")); + assertNull("invalidated valid name with 0x00B7", Verifier.checkAttributeName("test" + (char)0x00B7)); + assertNull("invalidated valid name with 0x4E01", Verifier.checkAttributeName("test" + (char)0x4E01)); + assertNull("invalidated valid name with 0x0301", Verifier.checkAttributeName("test" + (char)0x0301)); + + assertNull(Verifier.checkAttributeName("hi")); + assertNull(Verifier.checkAttributeName("hi2you")); + assertNull(Verifier.checkAttributeName("hi_you")); + + assertNotNull(Verifier.checkAttributeName(null)); + assertNotNull(Verifier.checkAttributeName("")); + assertNotNull(Verifier.checkAttributeName(" ")); + assertNotNull(Verifier.checkAttributeName(" hi ")); + assertNotNull(Verifier.checkAttributeName("hi ")); + assertNotNull(Verifier.checkAttributeName(" hi")); + assertNotNull(Verifier.checkAttributeName("2bad")); + + } + + /** + * Test that a String contains only xml characters. The method under + * only checks for null values and then character by character scans + * the string so this test is not exhaustive + */ + @Test + public void testCheckCharacterData() { + //check out of range values + assertNotNull("validated invalid null", Verifier.checkCharacterData(null)); + assertNotNull("validated invalid string with null", Verifier.checkCharacterData("test" + (char)0x0)); + assertNotNull("validated invalid string with null", Verifier.checkCharacterData("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid string with null", Verifier.checkCharacterData((char)0x0 + "test")); + assertNotNull("validated invalid string with 0x01", Verifier.checkCharacterData((char)0x01 + "test")); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCharacterData("test" + (char)0xD800)); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCharacterData("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCharacterData((char)0xD800 + "test")); + + //various valid strings + assertNull("invalidated valid string with \n", Verifier.checkCharacterData("test" + '\n' + "ing")); + assertNull("invalidated valid string with 0x29", Verifier.checkCharacterData("test" +(char)0x29)); + //a few higher values + assertNull("invalidated valid string with 0x0B08", Verifier.checkCharacterData("test" + (char)0x0B08)); + assertNull("invalidated valid string with \t", Verifier.checkCharacterData("test" + '\t')); + //xml letter + assertNull("invalidated valid string with 0x42", Verifier.checkCharacterData("test" + (char)0x42)); + assertNull("invalidated valid string with 0x4E01", Verifier.checkCharacterData("test" + (char)0x4E01)); + + } + + /** + * Test that checkCDATASection verifies CDATA excluding + * the closing delimiter. + */ + @Test + public void testCheckCDATASection() { + //check out of range values + assertNotNull("validated invalid null", Verifier.checkCDATASection(null)); + assertNotNull("validated invalid string with null", Verifier.checkCDATASection("test" + (char)0x0)); + assertNotNull("validated invalid string with null", Verifier.checkCDATASection("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid string with null", Verifier.checkCDATASection((char)0x0 + "test")); + assertNotNull("validated invalid string with 0x01", Verifier.checkCDATASection((char)0x01 + "test")); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCDATASection("test" + (char)0xD800)); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCDATASection("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCDATASection((char)0xD800 + "test")); + assertNotNull("validated invalid string with ]]>", Verifier.checkCDATASection("test]]>")); + + //various valid strings + assertNull("invalidated valid string with \n", Verifier.checkCDATASection("test" + '\n' + "ing")); + assertNull("invalidated valid string with 0x29", Verifier.checkCDATASection("test" +(char)0x29)); + assertNull("invalidated valid string with ]", Verifier.checkCDATASection("test]")); + assertNull("invalidated valid string with [", Verifier.checkCDATASection("test[")); + //a few higher values + assertNull("invalidated valid string with 0x0B08", Verifier.checkCDATASection("test" + (char)0x0B08)); + assertNull("invalidated valid string with \t", Verifier.checkCDATASection("test" + '\t')); + //xml letter + assertNull("invalidated valid string with 0x42", Verifier.checkCDATASection("test" + (char)0x42)); + assertNull("invalidated valid string with 0x4E01", Verifier.checkCDATASection("test" + (char)0x4E01)); + + } + + /** + * Test that checkNamespacePrefix validates against xml names + * with the following exceptions. Prefix names must not start + * with "-", "xmlns", digits, "$", or "." and must not contain + * ":" + */ + @Test + public void testCheckNamespacePrefix() { + //check out of range values + assertNotNull("validated invalid name with null", Verifier.checkNamespacePrefix("test" + (char)0x0)); + assertNotNull("validated invalid name with null", Verifier.checkNamespacePrefix("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid name with null", Verifier.checkNamespacePrefix((char)0x0 + "test")); + assertNotNull("validated invalid name with 0x01", Verifier.checkNamespacePrefix((char)0x01 + "test")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkNamespacePrefix("test" + (char)0xD800)); + assertNotNull("validated invalid name with 0xD800", Verifier.checkNamespacePrefix("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkNamespacePrefix((char)0xD800 + "test")); + assertNotNull("validated invalid name with :", Verifier.checkNamespacePrefix("test" + ':' + "local")); + + //invalid start characters + assertNotNull("validated invalid name with startin -", Verifier.checkNamespacePrefix('-' + "test")); + assertNotNull("validated invalid name with startin :", Verifier.checkNamespacePrefix(':' + "test")); + assertNotNull("validated invalid name with starting digit", Verifier.checkNamespacePrefix("9")); + assertNotNull("validated invalid name with starting $", Verifier.checkNamespacePrefix("$")); + assertNotNull("validated invalid name with starting .", Verifier.checkNamespacePrefix(".")); + + // cannot start with xml (case insensitive). + // See issue 126: https://github.com/hunterhacker/jdom/issues/126 +// assertNotNull("validated invalid name with xmlns", Verifier.checkNamespacePrefix("xmlns")); +// assertNotNull("validated invalid name beginning with xml", Verifier.checkNamespacePrefix("xmlabc")); +// assertNotNull("validated invalid name beginning with xml", Verifier.checkNamespacePrefix("xmLabc")); +// assertNotNull("validated invalid name beginning with xml", Verifier.checkNamespacePrefix("xMlabc")); +// assertNotNull("validated invalid name beginning with xml", Verifier.checkNamespacePrefix("Xmlabc")); + + + //valid tests + assertNull("invalidated valid null", Verifier.checkNamespacePrefix(null)); + assertNull("invalidated valid empty String", Verifier.checkNamespacePrefix("")); + assertNull("invalidated valid name with starting _", Verifier.checkNamespacePrefix('_' + "test")); + assertNull("invalidated valid name with _", Verifier.checkNamespacePrefix("test" + '_')); + assertNull("invalidated valid name with .", Verifier.checkNamespacePrefix("test" + '.' + "name")); + assertNull("invalidated valid name with digit", Verifier.checkNamespacePrefix("test9")); + assertNull("invalidated valid name with 0x00B7", Verifier.checkNamespacePrefix("test" + (char)0x00B7)); + assertNull("invalidated valid name with 0x4E01", Verifier.checkNamespacePrefix("test" + (char)0x4E01)); + assertNull("invalidated valid name with 0x0301", Verifier.checkNamespacePrefix("test" + (char)0x0301)); + assertNull("invalidated valid name with xml embedded", Verifier.checkNamespacePrefix("txml")); + assertNull("invalidated valid name with xml embedded", Verifier.checkNamespacePrefix("xmml")); + + // These tests all used to be NotNull tests, but the Verifier as been changed to pass them + // See issue 126: https://github.com/hunterhacker/jdom/issues/126 + assertNull("invalidated invalid name with xmlns", Verifier.checkNamespacePrefix("xmlns")); + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("xmlabc")); + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("xmLabc")); + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("xMlabc")); + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("Xmlabc")); + + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("XML")); + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("XMLNS")); + assertNull("invalidated invalid name beginning with xml", Verifier.checkNamespacePrefix("XmL")); + } + + /** + * Tests that checkNamespaceURI validates xml uri's. + * A valid URI is alphanumeric characters and the reserved characters: + * ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," + * + * The URI cannot begin with a digit, "-" or "$". It must have at least + * one ":" separating the scheme from the scheme specific part + * + * XXX:TODO make this match the eventual specs for the Verifier class which is incomplete + */ + @Test + public void testCheckNamespaceURI() { + //invalid start characters + assertNotNull("validated invalid URI with startin -", Verifier.checkNamespaceURI('-' + "test")); + assertNotNull("validated invalid URI with starting digit", Verifier.checkNamespaceURI("9")); + assertNotNull("validated invalid URI with starting $", Verifier.checkNamespaceURI("$")); + + //valid tests + assertNull("invalidated valid null", Verifier.checkNamespaceURI(null)); + assertNull("invalidated valid null", Verifier.checkNamespaceURI("")); + assertNull("invalidated valid URI with :", Verifier.checkNamespaceURI("test" + ':' + "local")); + assertNull("invalidated valid URI with _", Verifier.checkNamespaceURI("test" + '_')); + assertNull("invalidated valid URI with .", Verifier.checkNamespaceURI("test" + '.' + "URI")); + assertNull("invalidated valid URI with digit", Verifier.checkNamespaceURI("test9")); + assertNull("invalidated valid URI with 0x00B7", Verifier.checkNamespaceURI("test" + (char)0x00B7)); + assertNull("invalidated valid URI with 0x4E01", Verifier.checkNamespaceURI("test" + (char)0x4E01)); + assertNull("invalidated valid URI with 0x0301", Verifier.checkNamespaceURI("test" + (char)0x0301)); + + //check out of range values + + /** skip these tests until the time the checks are implemented + assertNull("validated invalid URI with xmlns", Verifier.checkNamespaceURI("xmlns")); + assertNull("validated invalid URI with startin :", Verifier.checkNamespaceURI(':' + "test")); + assertNull("validated invalid URI with starting .", Verifier.checkNamespaceURI(".")); + + assertNull("validated invalid URI with null", Verifier.checkNamespaceURI("test" + (char)0x0)); + assertNull("validated invalid URI with null", Verifier.checkNamespaceURI("test" + (char)0x0 + "ing")); + assertNull("validated invalid URI with null", Verifier.checkNamespaceURI((char)0x0 + "test")); + assertNull("validated invalid URI with 0x01", Verifier.checkNamespaceURI((char)0x01 + "test")); + assertNull("validated invalid URI with 0xD800", Verifier.checkNamespaceURI("test" + (char)0xD800)); + assertNull("validated invalid URI with 0xD800", Verifier.checkNamespaceURI("test" + (char)0xD800 + "ing")); + assertNull("validated invalid URI with 0xD800", Verifier.checkNamespaceURI((char)0xD800 + "test")); + */ + } + + /** + * Test that checkProcessintInstructionTarget validates the name + * of a processing instruction. This name must be a normal xml + * and cannot have ":" or "xml" in the name. + */ + @Test + public void testCheckProcessingInstructionTarget() { + //check out of range values + assertNotNull("validated invalid null", Verifier.checkProcessingInstructionTarget(null)); + assertNotNull("validated invalid name with null", Verifier.checkProcessingInstructionTarget("test" + (char)0x0)); + assertNotNull("validated invalid name with null", Verifier.checkProcessingInstructionTarget("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid name with null", Verifier.checkProcessingInstructionTarget((char)0x0 + "test")); + assertNotNull("validated invalid name with 0x01", Verifier.checkProcessingInstructionTarget((char)0x01 + "test")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkProcessingInstructionTarget("test" + (char)0xD800)); + assertNotNull("validated invalid name with 0xD800", Verifier.checkProcessingInstructionTarget("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid name with 0xD800", Verifier.checkProcessingInstructionTarget((char)0xD800 + "test")); + assertNotNull("validated invalid name with :", Verifier.checkProcessingInstructionTarget("test" + ':' + "local")); + assertNotNull("validated invalid name with xml:space", Verifier.checkProcessingInstructionTarget("xml:space")); + assertNotNull("validated invalid name with xml:lang", Verifier.checkProcessingInstructionTarget("xml:lang")); + assertNotNull("validated invalid name with xml", Verifier.checkProcessingInstructionTarget("xml")); + assertNotNull("validated invalid name with xMl", Verifier.checkProcessingInstructionTarget("xMl")); + + //invalid start characters + assertNotNull("validated invalid name with startin -", Verifier.checkProcessingInstructionTarget('-' + "test")); + assertNotNull("validated invalid name with startin :", Verifier.checkProcessingInstructionTarget(':' + "test")); + //valid tests + assertNull("invalidated valid name with starting _", Verifier.checkProcessingInstructionTarget('_' + "test")); + assertNull("invalidated valid name with _", Verifier.checkProcessingInstructionTarget("test" + '_')); + assertNull("invalidated valid name with .", Verifier.checkProcessingInstructionTarget("test" + '.' + "name")); + assertNull("invalidated valid name with 0x00B7", Verifier.checkProcessingInstructionTarget("test" + (char)0x00B7)); + assertNull("invalidated valid name with 0x4E01", Verifier.checkProcessingInstructionTarget("test" + (char)0x4E01)); + assertNull("invalidated valid name with 0x0301", Verifier.checkProcessingInstructionTarget("test" + (char)0x0301)); + + } + + @Test + public void testCheckProcessingInstructionData() { + assertNull(Verifier.checkProcessingInstructionData("")); + assertNull(Verifier.checkProcessingInstructionData(" ")); + assertNull(Verifier.checkProcessingInstructionData("hi")); + assertNull(Verifier.checkProcessingInstructionData(" h i ")); + + assertNotNull(Verifier.checkProcessingInstructionData(null)); + assertNotNull(Verifier.checkProcessingInstructionData("hi" + (char)0x0b + " there")); + assertNotNull(Verifier.checkProcessingInstructionData("can't have '?>' in text.")); + } + + /** + * Test checkCommentData such that a comment is validated as an + * xml comment consisting of xml characters with the following caveats. + * The comment must not contain a double hyphen. + */ + @Test + public void testCheckCommentData() { + //check out of range values + assertNotNull("validated invalid null", Verifier.checkCommentData(null)); + assertNotNull("validated invalid string with null", Verifier.checkCommentData("test" + (char)0x0)); + assertNotNull("validated invalid string with null", Verifier.checkCommentData("test" + (char)0x0 + "ing")); + assertNotNull("validated invalid string with null", Verifier.checkCommentData((char)0x0 + "test")); + assertNotNull("validated invalid string with 0x01", Verifier.checkCommentData((char)0x01 + "test")); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCommentData("test" + (char)0xD800)); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCommentData("test" + (char)0xD800 + "ing")); + assertNotNull("validated invalid string with 0xD800", Verifier.checkCommentData((char)0xD800 + "test")); + assertNotNull("validated invalid string with --", Verifier.checkCommentData("--test")); + assertNotNull("validated invalid string ending with -", Verifier.checkCommentData("test-")); + + //various valid strings + assertNull("invalidated valid string with \n", Verifier.checkCommentData("test" + '\n' + "ing")); + assertNull("invalidated valid string with 0x29", Verifier.checkCommentData("test" +(char)0x29)); + //a few higher values + assertNull("invalidated valid string with 0x0B08", Verifier.checkCommentData("test" + (char)0x0B08)); + assertNull("invalidated valid string with \t", Verifier.checkCommentData("test" + '\t')); + //xml letter + assertNull("invalidated valid string with 0x42", Verifier.checkCommentData("test" + (char)0x42)); + assertNull("invalidated valid string with 0x4E01", Verifier.checkCommentData("test" + (char)0x4E01)); + + } + + @Test + public void testCheckNamespaceCollision() { + try { + Namespace ns1 = Namespace.getNamespace("aaa", "http://acme.com/aaa"); + Element e = new Element("aaa", ns1); + e.setAttribute("att1", "att1"); + Namespace defns = Namespace.getNamespace("http://acme.com/default"); + e.addNamespaceDeclaration(defns); + // pass + } catch (Exception e) { + // also see http://markmail.org/message/hvzz73em7ztt5i5k + fail("Bug http://www.junlu.com/msg/166290.html"); + } + + Namespace nsnp = Namespace.getNamespace("rootns"); + Namespace nswp = Namespace.getNamespace("p", nsnp.getURI()); + Namespace mscp = Namespace.getNamespace("p", "childns"); + Namespace ans = Namespace.getNamespace("a", "attns"); + + // no collision between Namespace and itself + assertNull(Verifier.checkNamespaceCollision(nsnp, nsnp)); + + // no collision between Namespace and other namespace with different prefix + assertNull(Verifier.checkNamespaceCollision(nsnp, nswp)); + + // but collision between same prefix, but different URI + assertNotNull(Verifier.checkNamespaceCollision(nswp, mscp)); + + Element root = new Element("root", nsnp); + root.addNamespaceDeclaration(nswp); + Attribute att = new Attribute("att", "val", nswp); + + // should be able to add + assertNull(Verifier.checkNamespaceCollision(att, root)); + root.setAttribute(att); + + root.setAttribute(new Attribute("ans", "v", ans)); + + // cnns is child no namespace + Element cnns = new Element ("cnns"); + root.addContent(cnns); + // cwns is child with namespace + Element cwns = new Element ("cwns", nsnp); + root.addContent(cwns); + // cpns is child prefixed namespace + Element cpns = new Element ("cpns", nswp); + root.addContent(cpns); + // cpms is child prefixed different namespace + Element cpms = new Element ("cpms", mscp); + root.addContent(cpms); + + //printlement(root); +// XMLOutputter out = new XMLOutputter(Format.getPrettyFormat()); +// try { +// CharArrayWriter w = new CharArrayWriter(); +// out.output(root, w); +// } catch (IOException e) { +// e.printStackTrace(); +// } + + + // Check Namespace against elements. + assertNull (Verifier.checkNamespaceCollision(att, root)); + assertNull (Verifier.checkNamespaceCollision(att, cnns)); + assertNull (Verifier.checkNamespaceCollision(att, cwns)); + assertNull (Verifier.checkNamespaceCollision(att, cpns)); + + // now, check it against the child that redefines the 'p' prefix. + assertNotNull(Verifier.checkNamespaceCollision(nswp, cpms)); + // root has an attribute with the namspace a->ans, cannot then have a->dummy + assertNotNull(Verifier.checkNamespaceCollision(Namespace.getNamespace("a", "dummy"), root)); + // root has an additional namespace p->rootns, can't then have a different 'p' + assertNotNull(Verifier.checkNamespaceCollision(Namespace.getNamespace("p", "dummy"), root)); + + // Check Namespace against Attributes + // we do the same tests as above, only we wrap it in an Attribute + // first, one that passes. + assertNull(Verifier.checkNamespaceCollision(att, cwns)); + // now, check it against the child that redefines the 'p' prefix. + assertNotNull(Verifier.checkNamespaceCollision(att, cpms)); + // root has an attribute with the namspace a->ans, cannot then have a->dummy + assertNotNull(Verifier.checkNamespaceCollision( + new Attribute("ax", "v", Namespace.getNamespace("a", "dummy")), root)); + // root has an additional namespace p->rootns, can't then have a different 'p' + assertNotNull(Verifier.checkNamespaceCollision( + new Attribute("ax", "v", Namespace.getNamespace("p", "dummy")), root)); + + + // now we need to check the namespaces against Attributes not Elements + // first, one that works + assertNull(Verifier.checkNamespaceCollision(nswp, new Attribute("foo", "bar"))); + // now, check it against the child that redefines the 'p' prefix. + assertNotNull(Verifier.checkNamespaceCollision(nswp, new Attribute("foo", "bar", mscp))); + + // Now test some List structures. + // first, one that passes. + assertNull(Verifier.checkNamespaceCollision(nswp, root.getAdditionalNamespaces())); + // then, a passing one... with junk... + List c = null; + assertNull(Verifier.checkNamespaceCollision(nswp, c)); + c = new ArrayList(); + c.addAll(Arrays.asList(root.getAdditionalNamespaces().toArray())); + c.add(Integer.valueOf(1)); + c.add(0, "dummy"); + assertNull(Verifier.checkNamespaceCollision(nswp, c)); + + // now, check something that will conflict... check against a conflicting attribute. + // which is already in there. + assertNotNull(Verifier.checkNamespaceCollision(mscp, c)); + + // now check against an Element. + // replace prefixed namespace with prefixed Element + c.set(c.indexOf(nswp), cpns); + assertNotNull(Verifier.checkNamespaceCollision(mscp, c)); + + // now replace prefixed Element with prefixed Attribute + c.set(c.indexOf(cpns), new Attribute("a", "b", nswp)); + assertNotNull(Verifier.checkNamespaceCollision(mscp, c)); + + + // some basic namespace/attribute tests similar to the ones done for the bug test up top. + // attributes with no prefix can never collide... because attributes are always + // in the NO_NAMESPACE unless prefixed. + assertNull(Verifier.checkNamespaceCollision(new Attribute("a", "b"), root)); + // att is already on root, can't fail. + assertNull(Verifier.checkNamespaceCollision(att, root)); + // but, we can fail an attribute on the child with changed p prefix + assertNotNull(Verifier.checkNamespaceCollision(new Attribute("a", "b", mscp), root)); + + } + + @Test + public void testCheckPublicID() { + assertNull("invalidated valid publicid: null", Verifier.checkPublicID(null)); + assertNull("invalidated valid publicid: ''", Verifier.checkPublicID("")); + assertNull("invalidated valid publicid: ' '", Verifier.checkPublicID(" ")); + assertNull("invalidated valid publicid: contains \"'\"", + Verifier.checkPublicID("shroedinger's cat was here")); + + assertNotNull(Verifier.checkPublicID("cannot have " + BADCHAR + " characters here")); + + } + + @Test + public void testCheckXMLName() { + assertNull(Verifier.checkXMLName("hi")); + assertNull(Verifier.checkXMLName("hi2you")); + assertNull(Verifier.checkXMLName("hi_you")); + assertNull(Verifier.checkXMLName("hi:you")); + + assertNotNull(Verifier.checkXMLName(null)); + assertNotNull(Verifier.checkXMLName("")); + assertNotNull(Verifier.checkXMLName(" ")); + assertNotNull(Verifier.checkXMLName(" hi ")); + assertNotNull(Verifier.checkXMLName("hi ")); + assertNotNull(Verifier.checkXMLName(" hi")); + assertNotNull(Verifier.checkXMLName("2bad")); + } + + @Test + public void testCheckSystemLiteral() { + assertNull(Verifier.checkSystemLiteral(null)); + assertNull(Verifier.checkSystemLiteral("")); + assertNull(Verifier.checkSystemLiteral(" ")); + assertNull(Verifier.checkSystemLiteral("frodo's theme ")); + assertNull(Verifier.checkSystemLiteral("frodo has a \"theme\" ")); + + assertNotNull(Verifier.checkSystemLiteral("frodo's \"theme\" ")); + + } + + + @Test + public void testCheckURI() { + assertNull(Verifier.checkURI(null)); + assertNull(Verifier.checkURI("")); + assertNull(Verifier.checkURI("http://www.jdom.org/index.html")); + assertNull(Verifier.checkURI("http://www.jdom.org:321/index.html")); + assertNull(Verifier.checkURI("http://www.jdom.org%32%01/index.html?%ab")); + assertNull(Verifier.checkURI("http://www.jdom.org/index.html%31")); + + assertNotNull(Verifier.checkURI("http://www.jdom.org/ index.html")); + assertNotNull(Verifier.checkURI(" http://www.jdom.org/index.html ")); + assertNotNull(Verifier.checkURI("http://www.jdom.org%3.21/index.html")); + assertNotNull(Verifier.checkURI("http://www.jdom.org%.21/index.html")); + assertNotNull(Verifier.checkURI("http://www.jdom.org%3g21/index.html")); + assertNotNull(Verifier.checkURI("http://www.jdom.org%3/index.html")); + assertNotNull(Verifier.checkURI("http://www.jdom.org" + BADCHAR + "/index.html")); + assertNotNull(Verifier.checkURI("http://www.jdom.org" + (char)0x05 + "/index.html")); + assertNotNull(Verifier.checkURI("http://www.jdom.org/index.html%3")); + } + + @Test + public void testIsXMLCharacter() { + // this test is not part of the automatic tests because it takes an int + // as an argument, instead of a char. + // cherry-pick some tests. + assertTrue(Verifier.isXMLCharacter('\n')); + assertTrue(Verifier.isXMLCharacter('\r')); + assertTrue(Verifier.isXMLCharacter('\t')); + assertTrue(Verifier.isXMLCharacter(' ')); + assertTrue(Verifier.isXMLCharacter(0xd7ff)); + assertTrue(Verifier.isXMLCharacter(0xe000)); + assertTrue(Verifier.isXMLCharacter(0x10000)); + + //cherry-pick values we know will fill out the coverage report. + assertFalse(Verifier.isXMLCharacter(0)); + assertFalse(Verifier.isXMLCharacter(0x19)); + assertFalse(Verifier.isXMLCharacter(0xd800)); + assertFalse(Verifier.isXMLCharacter(0xffff)); + assertFalse(Verifier.isXMLCharacter(0x110000)); + + } + + @Test + public void testIsAllXMLWhitespace() { + assertTrue(Verifier.isAllXMLWhitespace("")); + assertTrue(Verifier.isAllXMLWhitespace(" ")); + assertTrue(Verifier.isAllXMLWhitespace(" \r\n\t ")); + try { + Verifier.isAllXMLWhitespace(null); + fail("Expected a NullPointerException, but it did not happen"); + } catch (NullPointerException npe) { + // good. + } + assertFalse(Verifier.isAllXMLWhitespace(" a ")); + assertFalse(Verifier.isAllXMLWhitespace("   ")); + // \u00A0 is Non-Break space. + assertFalse(Verifier.isAllXMLWhitespace("\u00A0")); + } + +} diff --git a/test/src/java/org/jdom/test/cases/TestVerifierCharacters.java b/test/src/java/org/jdom/test/cases/TestVerifierCharacters.java new file mode 100644 index 0000000..518c708 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/TestVerifierCharacters.java @@ -0,0 +1,538 @@ +package org.jdom.test.cases; + +import static org.junit.Assert.*; + +import org.jdom.Verifier; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestVerifierCharacters { + + // Automated test built by VerifierTestBuilder + @Test + public void testIsHighSurrogate () { + final int[] flips = new int[] { + + 0xd800, 0xdc00 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isHighSurrogate((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isHighSurrogate but it failed."); + } + } else { + if (Verifier.isHighSurrogate((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isHighSurrogate but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsHighSurrogate in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsLowSurrogate () { + final int[] flips = new int[] { + + 0xdc00, 0xe000 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isLowSurrogate((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isLowSurrogate but it failed."); + } + } else { + if (Verifier.isLowSurrogate((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isLowSurrogate but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsLowSurrogate in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLDigit () { + final int[] flips = new int[] { + + 0x0030, 0x003a, 0x0660, 0x066a, 0x06f0, 0x06fa, 0x0966, 0x0970, 0x09e6, 0x09f0, 0x0a66, 0x0a70, 0x0ae6, 0x0af0, 0x0b66, 0x0b70, + 0x0be7, 0x0bf0, 0x0c66, 0x0c70, 0x0ce6, 0x0cf0, 0x0d66, 0x0d70, 0x0e50, 0x0e5a, 0x0ed0, 0x0eda, 0x0f20, 0x0f2a + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLDigit((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLDigit but it failed."); + } + } else { + if (Verifier.isXMLDigit((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLDigit but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLDigit in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLNameCharacter () { + final int[] flips = new int[] { + + 0x002d, 0x002f, 0x0030, 0x003b, 0x0041, 0x005b, 0x005f, 0x0060, 0x0061, 0x007b, 0x00b7, 0x00b8, 0x00c0, 0x00d7, 0x00d8, 0x00f7, + 0x00f8, 0x0132, 0x0134, 0x013f, 0x0141, 0x0149, 0x014a, 0x017f, 0x0180, 0x01c4, 0x01cd, 0x01f1, 0x01f4, 0x01f6, 0x01fa, 0x0218, + 0x0250, 0x02a9, 0x02bb, 0x02c2, 0x02d0, 0x02d2, 0x0300, 0x0346, 0x0360, 0x0362, 0x0386, 0x038b, 0x038c, 0x038d, 0x038e, 0x03a2, + 0x03a3, 0x03cf, 0x03d0, 0x03d7, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, 0x03e0, 0x03e1, 0x03e2, 0x03f4, 0x0401, 0x040d, + 0x040e, 0x0450, 0x0451, 0x045d, 0x045e, 0x0482, 0x0483, 0x0487, 0x0490, 0x04c5, 0x04c7, 0x04c9, 0x04cb, 0x04cd, 0x04d0, 0x04ec, + 0x04ee, 0x04f6, 0x04f8, 0x04fa, 0x0531, 0x0557, 0x0559, 0x055a, 0x0561, 0x0587, 0x0591, 0x05a2, 0x05a3, 0x05ba, 0x05bb, 0x05be, + 0x05bf, 0x05c0, 0x05c1, 0x05c3, 0x05c4, 0x05c5, 0x05d0, 0x05eb, 0x05f0, 0x05f3, 0x0621, 0x063b, 0x0640, 0x0653, 0x0660, 0x066a, + 0x0670, 0x06b8, 0x06ba, 0x06bf, 0x06c0, 0x06cf, 0x06d0, 0x06d4, 0x06d5, 0x06e9, 0x06ea, 0x06ee, 0x06f0, 0x06fa, 0x0901, 0x0904, + 0x0905, 0x093a, 0x093c, 0x094e, 0x0951, 0x0955, 0x0958, 0x0964, 0x0966, 0x0970, 0x0981, 0x0984, 0x0985, 0x098d, 0x098f, 0x0991, + 0x0993, 0x09a9, 0x09aa, 0x09b1, 0x09b2, 0x09b3, 0x09b6, 0x09ba, 0x09bc, 0x09bd, 0x09be, 0x09c5, 0x09c7, 0x09c9, 0x09cb, 0x09ce, + 0x09d7, 0x09d8, 0x09dc, 0x09de, 0x09df, 0x09e4, 0x09e6, 0x09f2, 0x0a02, 0x0a03, 0x0a05, 0x0a0b, 0x0a0f, 0x0a11, 0x0a13, 0x0a29, + 0x0a2a, 0x0a31, 0x0a32, 0x0a34, 0x0a35, 0x0a37, 0x0a38, 0x0a3a, 0x0a3c, 0x0a3d, 0x0a3e, 0x0a43, 0x0a47, 0x0a49, 0x0a4b, 0x0a4e, + 0x0a59, 0x0a5d, 0x0a5e, 0x0a5f, 0x0a66, 0x0a75, 0x0a81, 0x0a84, 0x0a85, 0x0a8c, 0x0a8d, 0x0a8e, 0x0a8f, 0x0a92, 0x0a93, 0x0aa9, + 0x0aaa, 0x0ab1, 0x0ab2, 0x0ab4, 0x0ab5, 0x0aba, 0x0abc, 0x0ac6, 0x0ac7, 0x0aca, 0x0acb, 0x0ace, 0x0ae0, 0x0ae1, 0x0ae6, 0x0af0, + 0x0b01, 0x0b04, 0x0b05, 0x0b0d, 0x0b0f, 0x0b11, 0x0b13, 0x0b29, 0x0b2a, 0x0b31, 0x0b32, 0x0b34, 0x0b36, 0x0b3a, 0x0b3c, 0x0b44, + 0x0b47, 0x0b49, 0x0b4b, 0x0b4e, 0x0b56, 0x0b58, 0x0b5c, 0x0b5e, 0x0b5f, 0x0b62, 0x0b66, 0x0b70, 0x0b82, 0x0b84, 0x0b85, 0x0b8b, + 0x0b8e, 0x0b91, 0x0b92, 0x0b96, 0x0b99, 0x0b9b, 0x0b9c, 0x0b9d, 0x0b9e, 0x0ba0, 0x0ba3, 0x0ba5, 0x0ba8, 0x0bab, 0x0bae, 0x0bb6, + 0x0bb7, 0x0bba, 0x0bbe, 0x0bc3, 0x0bc6, 0x0bc9, 0x0bca, 0x0bce, 0x0bd7, 0x0bd8, 0x0be7, 0x0bf0, 0x0c01, 0x0c04, 0x0c05, 0x0c0d, + 0x0c0e, 0x0c11, 0x0c12, 0x0c29, 0x0c2a, 0x0c34, 0x0c35, 0x0c3a, 0x0c3e, 0x0c45, 0x0c46, 0x0c49, 0x0c4a, 0x0c4e, 0x0c55, 0x0c57, + 0x0c60, 0x0c62, 0x0c66, 0x0c70, 0x0c82, 0x0c84, 0x0c85, 0x0c8d, 0x0c8e, 0x0c91, 0x0c92, 0x0ca9, 0x0caa, 0x0cb4, 0x0cb5, 0x0cba, + 0x0cbe, 0x0cc5, 0x0cc6, 0x0cc9, 0x0cca, 0x0cce, 0x0cd5, 0x0cd7, 0x0cde, 0x0cdf, 0x0ce0, 0x0ce2, 0x0ce6, 0x0cf0, 0x0d02, 0x0d04, + 0x0d05, 0x0d0d, 0x0d0e, 0x0d11, 0x0d12, 0x0d29, 0x0d2a, 0x0d3a, 0x0d3e, 0x0d44, 0x0d46, 0x0d49, 0x0d4a, 0x0d4e, 0x0d57, 0x0d58, + 0x0d60, 0x0d62, 0x0d66, 0x0d70, 0x0e01, 0x0e2f, 0x0e30, 0x0e3b, 0x0e40, 0x0e4f, 0x0e50, 0x0e5a, 0x0e81, 0x0e83, 0x0e84, 0x0e85, + 0x0e87, 0x0e89, 0x0e8a, 0x0e8b, 0x0e8d, 0x0e8e, 0x0e94, 0x0e98, 0x0e99, 0x0ea0, 0x0ea1, 0x0ea4, 0x0ea5, 0x0ea6, 0x0ea7, 0x0ea8, + 0x0eaa, 0x0eac, 0x0ead, 0x0eaf, 0x0eb0, 0x0eba, 0x0ebb, 0x0ebe, 0x0ec0, 0x0ec5, 0x0ec6, 0x0ec7, 0x0ec8, 0x0ece, 0x0ed0, 0x0eda, + 0x0f18, 0x0f1a, 0x0f20, 0x0f2a, 0x0f35, 0x0f36, 0x0f37, 0x0f38, 0x0f39, 0x0f3a, 0x0f3e, 0x0f48, 0x0f49, 0x0f6a, 0x0f71, 0x0f85, + 0x0f86, 0x0f8c, 0x0f90, 0x0f96, 0x0f97, 0x0f98, 0x0f99, 0x0fae, 0x0fb1, 0x0fb8, 0x0fb9, 0x0fba, 0x10a0, 0x10c6, 0x10d0, 0x10f7, + 0x1100, 0x1101, 0x1102, 0x1104, 0x1105, 0x1108, 0x1109, 0x110a, 0x110b, 0x110d, 0x110e, 0x1113, 0x113c, 0x113d, 0x113e, 0x113f, + 0x1140, 0x1141, 0x114c, 0x114d, 0x114e, 0x114f, 0x1150, 0x1151, 0x1154, 0x1156, 0x1159, 0x115a, 0x115f, 0x1162, 0x1163, 0x1164, + 0x1165, 0x1166, 0x1167, 0x1168, 0x1169, 0x116a, 0x116d, 0x116f, 0x1172, 0x1174, 0x1175, 0x1176, 0x119e, 0x119f, 0x11a8, 0x11a9, + 0x11ab, 0x11ac, 0x11ae, 0x11b0, 0x11b7, 0x11b9, 0x11ba, 0x11bb, 0x11bc, 0x11c3, 0x11eb, 0x11ec, 0x11f0, 0x11f1, 0x11f9, 0x11fa, + 0x1e00, 0x1e9c, 0x1ea0, 0x1efa, 0x1f00, 0x1f16, 0x1f18, 0x1f1e, 0x1f20, 0x1f46, 0x1f48, 0x1f4e, 0x1f50, 0x1f58, 0x1f59, 0x1f5a, + 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f5f, 0x1f7e, 0x1f80, 0x1fb5, 0x1fb6, 0x1fbd, 0x1fbe, 0x1fbf, 0x1fc2, 0x1fc5, 0x1fc6, 0x1fcd, + 0x1fd0, 0x1fd4, 0x1fd6, 0x1fdc, 0x1fe0, 0x1fed, 0x1ff2, 0x1ff5, 0x1ff6, 0x1ffd, 0x20d0, 0x20dd, 0x20e1, 0x20e2, 0x2126, 0x2127, + 0x212a, 0x212c, 0x212e, 0x212f, 0x2180, 0x2183, 0x3005, 0x3006, 0x3007, 0x3008, 0x3021, 0x3030, 0x3031, 0x3036, 0x3041, 0x3095, + 0x3099, 0x309b, 0x309d, 0x309f, 0x30a1, 0x30fb, 0x30fc, 0x30ff, 0x3105, 0x312d, 0x4e00, 0x9fa6, 0xac00, 0xd7a4 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLNameCharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLNameCharacter but it failed."); + } + } else { + if (Verifier.isXMLNameCharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLNameCharacter but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLNameCharacter in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLPublicIDCharacter () { + final int[] flips = new int[] { + + 0x0009, 0x000b, 0x000d, 0x000e, 0x0020, 0x0022, 0x0023, 0x0026, 0x0027, 0x003c, 0x003d, 0x003e, 0x003f, 0x005b, 0x005f, 0x0060, + 0x0061, 0x007b + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLPublicIDCharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLPublicIDCharacter but it failed."); + } + } else { + if (Verifier.isXMLPublicIDCharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLPublicIDCharacter but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLPublicIDCharacter in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLNameStartCharacter () { + final int[] flips = new int[] { + + 0x003a, 0x003b, 0x0041, 0x005b, 0x005f, 0x0060, 0x0061, 0x007b, 0x00c0, 0x00d7, 0x00d8, 0x00f7, 0x00f8, 0x0132, 0x0134, 0x013f, + 0x0141, 0x0149, 0x014a, 0x017f, 0x0180, 0x01c4, 0x01cd, 0x01f1, 0x01f4, 0x01f6, 0x01fa, 0x0218, 0x0250, 0x02a9, 0x02bb, 0x02c2, + 0x0386, 0x0387, 0x0388, 0x038b, 0x038c, 0x038d, 0x038e, 0x03a2, 0x03a3, 0x03cf, 0x03d0, 0x03d7, 0x03da, 0x03db, 0x03dc, 0x03dd, + 0x03de, 0x03df, 0x03e0, 0x03e1, 0x03e2, 0x03f4, 0x0401, 0x040d, 0x040e, 0x0450, 0x0451, 0x045d, 0x045e, 0x0482, 0x0490, 0x04c5, + 0x04c7, 0x04c9, 0x04cb, 0x04cd, 0x04d0, 0x04ec, 0x04ee, 0x04f6, 0x04f8, 0x04fa, 0x0531, 0x0557, 0x0559, 0x055a, 0x0561, 0x0587, + 0x05d0, 0x05eb, 0x05f0, 0x05f3, 0x0621, 0x063b, 0x0641, 0x064b, 0x0671, 0x06b8, 0x06ba, 0x06bf, 0x06c0, 0x06cf, 0x06d0, 0x06d4, + 0x06d5, 0x06d6, 0x06e5, 0x06e7, 0x0905, 0x093a, 0x093d, 0x093e, 0x0958, 0x0962, 0x0985, 0x098d, 0x098f, 0x0991, 0x0993, 0x09a9, + 0x09aa, 0x09b1, 0x09b2, 0x09b3, 0x09b6, 0x09ba, 0x09dc, 0x09de, 0x09df, 0x09e2, 0x09f0, 0x09f2, 0x0a05, 0x0a0b, 0x0a0f, 0x0a11, + 0x0a13, 0x0a29, 0x0a2a, 0x0a31, 0x0a32, 0x0a34, 0x0a35, 0x0a37, 0x0a38, 0x0a3a, 0x0a59, 0x0a5d, 0x0a5e, 0x0a5f, 0x0a72, 0x0a75, + 0x0a85, 0x0a8c, 0x0a8d, 0x0a8e, 0x0a8f, 0x0a92, 0x0a93, 0x0aa9, 0x0aaa, 0x0ab1, 0x0ab2, 0x0ab4, 0x0ab5, 0x0aba, 0x0abd, 0x0abe, + 0x0ae0, 0x0ae1, 0x0b05, 0x0b0d, 0x0b0f, 0x0b11, 0x0b13, 0x0b29, 0x0b2a, 0x0b31, 0x0b32, 0x0b34, 0x0b36, 0x0b3a, 0x0b3d, 0x0b3e, + 0x0b5c, 0x0b5e, 0x0b5f, 0x0b62, 0x0b85, 0x0b8b, 0x0b8e, 0x0b91, 0x0b92, 0x0b96, 0x0b99, 0x0b9b, 0x0b9c, 0x0b9d, 0x0b9e, 0x0ba0, + 0x0ba3, 0x0ba5, 0x0ba8, 0x0bab, 0x0bae, 0x0bb6, 0x0bb7, 0x0bba, 0x0c05, 0x0c0d, 0x0c0e, 0x0c11, 0x0c12, 0x0c29, 0x0c2a, 0x0c34, + 0x0c35, 0x0c3a, 0x0c60, 0x0c62, 0x0c85, 0x0c8d, 0x0c8e, 0x0c91, 0x0c92, 0x0ca9, 0x0caa, 0x0cb4, 0x0cb5, 0x0cba, 0x0cde, 0x0cdf, + 0x0ce0, 0x0ce2, 0x0d05, 0x0d0d, 0x0d0e, 0x0d11, 0x0d12, 0x0d29, 0x0d2a, 0x0d3a, 0x0d60, 0x0d62, 0x0e01, 0x0e2f, 0x0e30, 0x0e31, + 0x0e32, 0x0e34, 0x0e40, 0x0e46, 0x0e81, 0x0e83, 0x0e84, 0x0e85, 0x0e87, 0x0e89, 0x0e8a, 0x0e8b, 0x0e8d, 0x0e8e, 0x0e94, 0x0e98, + 0x0e99, 0x0ea0, 0x0ea1, 0x0ea4, 0x0ea5, 0x0ea6, 0x0ea7, 0x0ea8, 0x0eaa, 0x0eac, 0x0ead, 0x0eaf, 0x0eb0, 0x0eb1, 0x0eb2, 0x0eb4, + 0x0ebd, 0x0ebe, 0x0ec0, 0x0ec5, 0x0f40, 0x0f48, 0x0f49, 0x0f6a, 0x10a0, 0x10c6, 0x10d0, 0x10f7, 0x1100, 0x1101, 0x1102, 0x1104, + 0x1105, 0x1108, 0x1109, 0x110a, 0x110b, 0x110d, 0x110e, 0x1113, 0x113c, 0x113d, 0x113e, 0x113f, 0x1140, 0x1141, 0x114c, 0x114d, + 0x114e, 0x114f, 0x1150, 0x1151, 0x1154, 0x1156, 0x1159, 0x115a, 0x115f, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167, 0x1168, + 0x1169, 0x116a, 0x116d, 0x116f, 0x1172, 0x1174, 0x1175, 0x1176, 0x119e, 0x119f, 0x11a8, 0x11a9, 0x11ab, 0x11ac, 0x11ae, 0x11b0, + 0x11b7, 0x11b9, 0x11ba, 0x11bb, 0x11bc, 0x11c3, 0x11eb, 0x11ec, 0x11f0, 0x11f1, 0x11f9, 0x11fa, 0x1e00, 0x1e9c, 0x1ea0, 0x1efa, + 0x1f00, 0x1f16, 0x1f18, 0x1f1e, 0x1f20, 0x1f46, 0x1f48, 0x1f4e, 0x1f50, 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, + 0x1f5f, 0x1f7e, 0x1f80, 0x1fb5, 0x1fb6, 0x1fbd, 0x1fbe, 0x1fbf, 0x1fc2, 0x1fc5, 0x1fc6, 0x1fcd, 0x1fd0, 0x1fd4, 0x1fd6, 0x1fdc, + 0x1fe0, 0x1fed, 0x1ff2, 0x1ff5, 0x1ff6, 0x1ffd, 0x2126, 0x2127, 0x212a, 0x212c, 0x212e, 0x212f, 0x2180, 0x2183, 0x3007, 0x3008, + 0x3021, 0x302a, 0x3041, 0x3095, 0x30a1, 0x30fb, 0x3105, 0x312d, 0x4e00, 0x9fa6, 0xac00, 0xd7a4 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLNameStartCharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLNameStartCharacter but it failed."); + } + } else { + if (Verifier.isXMLNameStartCharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLNameStartCharacter but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLNameStartCharacter in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsURICharacter () { + final int[] flips = new int[] { + + 0x0021, 0x0022, 0x0024, 0x003b, 0x003d, 0x003e, 0x003f, 0x005b, 0x005f, 0x0060, 0x0061, 0x007b, 0x007e, 0x007f + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isURICharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isURICharacter but it failed."); + } + } else { + if (Verifier.isURICharacter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isURICharacter but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsURICharacter in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsHexDigit () { + final int[] flips = new int[] { + + 0x0030, 0x003a, 0x0041, 0x0047, 0x0061, 0x0067 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isHexDigit((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isHexDigit but it failed."); + } + } else { + if (Verifier.isHexDigit((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isHexDigit but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsHexDigit in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLLetter () { + final int[] flips = new int[] { + + 0x0041, 0x005b, 0x0061, 0x007b, 0x00c0, 0x00d7, 0x00d8, 0x00f7, 0x00f8, 0x0132, 0x0134, 0x013f, 0x0141, 0x0149, 0x014a, 0x017f, + 0x0180, 0x01c4, 0x01cd, 0x01f1, 0x01f4, 0x01f6, 0x01fa, 0x0218, 0x0250, 0x02a9, 0x02bb, 0x02c2, 0x0386, 0x0387, 0x0388, 0x038b, + 0x038c, 0x038d, 0x038e, 0x03a2, 0x03a3, 0x03cf, 0x03d0, 0x03d7, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, 0x03e0, 0x03e1, + 0x03e2, 0x03f4, 0x0401, 0x040d, 0x040e, 0x0450, 0x0451, 0x045d, 0x045e, 0x0482, 0x0490, 0x04c5, 0x04c7, 0x04c9, 0x04cb, 0x04cd, + 0x04d0, 0x04ec, 0x04ee, 0x04f6, 0x04f8, 0x04fa, 0x0531, 0x0557, 0x0559, 0x055a, 0x0561, 0x0587, 0x05d0, 0x05eb, 0x05f0, 0x05f3, + 0x0621, 0x063b, 0x0641, 0x064b, 0x0671, 0x06b8, 0x06ba, 0x06bf, 0x06c0, 0x06cf, 0x06d0, 0x06d4, 0x06d5, 0x06d6, 0x06e5, 0x06e7, + 0x0905, 0x093a, 0x093d, 0x093e, 0x0958, 0x0962, 0x0985, 0x098d, 0x098f, 0x0991, 0x0993, 0x09a9, 0x09aa, 0x09b1, 0x09b2, 0x09b3, + 0x09b6, 0x09ba, 0x09dc, 0x09de, 0x09df, 0x09e2, 0x09f0, 0x09f2, 0x0a05, 0x0a0b, 0x0a0f, 0x0a11, 0x0a13, 0x0a29, 0x0a2a, 0x0a31, + 0x0a32, 0x0a34, 0x0a35, 0x0a37, 0x0a38, 0x0a3a, 0x0a59, 0x0a5d, 0x0a5e, 0x0a5f, 0x0a72, 0x0a75, 0x0a85, 0x0a8c, 0x0a8d, 0x0a8e, + 0x0a8f, 0x0a92, 0x0a93, 0x0aa9, 0x0aaa, 0x0ab1, 0x0ab2, 0x0ab4, 0x0ab5, 0x0aba, 0x0abd, 0x0abe, 0x0ae0, 0x0ae1, 0x0b05, 0x0b0d, + 0x0b0f, 0x0b11, 0x0b13, 0x0b29, 0x0b2a, 0x0b31, 0x0b32, 0x0b34, 0x0b36, 0x0b3a, 0x0b3d, 0x0b3e, 0x0b5c, 0x0b5e, 0x0b5f, 0x0b62, + 0x0b85, 0x0b8b, 0x0b8e, 0x0b91, 0x0b92, 0x0b96, 0x0b99, 0x0b9b, 0x0b9c, 0x0b9d, 0x0b9e, 0x0ba0, 0x0ba3, 0x0ba5, 0x0ba8, 0x0bab, + 0x0bae, 0x0bb6, 0x0bb7, 0x0bba, 0x0c05, 0x0c0d, 0x0c0e, 0x0c11, 0x0c12, 0x0c29, 0x0c2a, 0x0c34, 0x0c35, 0x0c3a, 0x0c60, 0x0c62, + 0x0c85, 0x0c8d, 0x0c8e, 0x0c91, 0x0c92, 0x0ca9, 0x0caa, 0x0cb4, 0x0cb5, 0x0cba, 0x0cde, 0x0cdf, 0x0ce0, 0x0ce2, 0x0d05, 0x0d0d, + 0x0d0e, 0x0d11, 0x0d12, 0x0d29, 0x0d2a, 0x0d3a, 0x0d60, 0x0d62, 0x0e01, 0x0e2f, 0x0e30, 0x0e31, 0x0e32, 0x0e34, 0x0e40, 0x0e46, + 0x0e81, 0x0e83, 0x0e84, 0x0e85, 0x0e87, 0x0e89, 0x0e8a, 0x0e8b, 0x0e8d, 0x0e8e, 0x0e94, 0x0e98, 0x0e99, 0x0ea0, 0x0ea1, 0x0ea4, + 0x0ea5, 0x0ea6, 0x0ea7, 0x0ea8, 0x0eaa, 0x0eac, 0x0ead, 0x0eaf, 0x0eb0, 0x0eb1, 0x0eb2, 0x0eb4, 0x0ebd, 0x0ebe, 0x0ec0, 0x0ec5, + 0x0f40, 0x0f48, 0x0f49, 0x0f6a, 0x10a0, 0x10c6, 0x10d0, 0x10f7, 0x1100, 0x1101, 0x1102, 0x1104, 0x1105, 0x1108, 0x1109, 0x110a, + 0x110b, 0x110d, 0x110e, 0x1113, 0x113c, 0x113d, 0x113e, 0x113f, 0x1140, 0x1141, 0x114c, 0x114d, 0x114e, 0x114f, 0x1150, 0x1151, + 0x1154, 0x1156, 0x1159, 0x115a, 0x115f, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167, 0x1168, 0x1169, 0x116a, 0x116d, 0x116f, + 0x1172, 0x1174, 0x1175, 0x1176, 0x119e, 0x119f, 0x11a8, 0x11a9, 0x11ab, 0x11ac, 0x11ae, 0x11b0, 0x11b7, 0x11b9, 0x11ba, 0x11bb, + 0x11bc, 0x11c3, 0x11eb, 0x11ec, 0x11f0, 0x11f1, 0x11f9, 0x11fa, 0x1e00, 0x1e9c, 0x1ea0, 0x1efa, 0x1f00, 0x1f16, 0x1f18, 0x1f1e, + 0x1f20, 0x1f46, 0x1f48, 0x1f4e, 0x1f50, 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f5f, 0x1f7e, 0x1f80, 0x1fb5, + 0x1fb6, 0x1fbd, 0x1fbe, 0x1fbf, 0x1fc2, 0x1fc5, 0x1fc6, 0x1fcd, 0x1fd0, 0x1fd4, 0x1fd6, 0x1fdc, 0x1fe0, 0x1fed, 0x1ff2, 0x1ff5, + 0x1ff6, 0x1ffd, 0x2126, 0x2127, 0x212a, 0x212c, 0x212e, 0x212f, 0x2180, 0x2183, 0x3007, 0x3008, 0x3021, 0x302a, 0x3041, 0x3095, + 0x30a1, 0x30fb, 0x3105, 0x312d, 0x4e00, 0x9fa6, 0xac00, 0xd7a4 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLLetter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLLetter but it failed."); + } + } else { + if (Verifier.isXMLLetter((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLLetter but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLLetter in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLCombiningChar () { + final int[] flips = new int[] { + + 0x0300, 0x0346, 0x0360, 0x0362, 0x0483, 0x0487, 0x0591, 0x05a2, 0x05a3, 0x05ba, 0x05bb, 0x05be, 0x05bf, 0x05c0, 0x05c1, 0x05c3, + 0x05c4, 0x05c5, 0x064b, 0x0653, 0x0670, 0x0671, 0x06d6, 0x06e5, 0x06e7, 0x06e9, 0x06ea, 0x06ee, 0x0901, 0x0904, 0x093c, 0x093d, + 0x093e, 0x094e, 0x0951, 0x0955, 0x0962, 0x0964, 0x0981, 0x0984, 0x09bc, 0x09bd, 0x09be, 0x09c5, 0x09c7, 0x09c9, 0x09cb, 0x09ce, + 0x09d7, 0x09d8, 0x09e2, 0x09e4, 0x0a02, 0x0a03, 0x0a3c, 0x0a3d, 0x0a3e, 0x0a43, 0x0a47, 0x0a49, 0x0a4b, 0x0a4e, 0x0a70, 0x0a72, + 0x0a81, 0x0a84, 0x0abc, 0x0abd, 0x0abe, 0x0ac6, 0x0ac7, 0x0aca, 0x0acb, 0x0ace, 0x0b01, 0x0b04, 0x0b3c, 0x0b3d, 0x0b3e, 0x0b44, + 0x0b47, 0x0b49, 0x0b4b, 0x0b4e, 0x0b56, 0x0b58, 0x0b82, 0x0b84, 0x0bbe, 0x0bc3, 0x0bc6, 0x0bc9, 0x0bca, 0x0bce, 0x0bd7, 0x0bd8, + 0x0c01, 0x0c04, 0x0c3e, 0x0c45, 0x0c46, 0x0c49, 0x0c4a, 0x0c4e, 0x0c55, 0x0c57, 0x0c82, 0x0c84, 0x0cbe, 0x0cc5, 0x0cc6, 0x0cc9, + 0x0cca, 0x0cce, 0x0cd5, 0x0cd7, 0x0d02, 0x0d04, 0x0d3e, 0x0d44, 0x0d46, 0x0d49, 0x0d4a, 0x0d4e, 0x0d57, 0x0d58, 0x0e31, 0x0e32, + 0x0e34, 0x0e3b, 0x0e47, 0x0e4f, 0x0eb1, 0x0eb2, 0x0eb4, 0x0eba, 0x0ebb, 0x0ebd, 0x0ec8, 0x0ece, 0x0f18, 0x0f1a, 0x0f35, 0x0f36, + 0x0f37, 0x0f38, 0x0f39, 0x0f3a, 0x0f3e, 0x0f40, 0x0f71, 0x0f85, 0x0f86, 0x0f8c, 0x0f90, 0x0f96, 0x0f97, 0x0f98, 0x0f99, 0x0fae, + 0x0fb1, 0x0fb8, 0x0fb9, 0x0fba, 0x20d0, 0x20dd, 0x20e1, 0x20e2, 0x302a, 0x3030, 0x3099, 0x309b + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLCombiningChar((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLCombiningChar but it failed."); + } + } else { + if (Verifier.isXMLCombiningChar((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLCombiningChar but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLCombiningChar in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLExtender () { + final int[] flips = new int[] { + + 0x00b7, 0x00b8, 0x02d0, 0x02d2, 0x0387, 0x0388, 0x0640, 0x0641, 0x0e46, 0x0e47, 0x0ec6, 0x0ec7, 0x3005, 0x3006, 0x3031, 0x3036, + 0x309d, 0x309f, 0x30fc, 0x30ff + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLExtender((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLExtender but it failed."); + } + } else { + if (Verifier.isXMLExtender((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLExtender but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLExtender in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLLetterOrDigit () { + final int[] flips = new int[] { + + 0x0030, 0x003a, 0x0041, 0x005b, 0x0061, 0x007b, 0x00c0, 0x00d7, 0x00d8, 0x00f7, 0x00f8, 0x0132, 0x0134, 0x013f, 0x0141, 0x0149, + 0x014a, 0x017f, 0x0180, 0x01c4, 0x01cd, 0x01f1, 0x01f4, 0x01f6, 0x01fa, 0x0218, 0x0250, 0x02a9, 0x02bb, 0x02c2, 0x0386, 0x0387, + 0x0388, 0x038b, 0x038c, 0x038d, 0x038e, 0x03a2, 0x03a3, 0x03cf, 0x03d0, 0x03d7, 0x03da, 0x03db, 0x03dc, 0x03dd, 0x03de, 0x03df, + 0x03e0, 0x03e1, 0x03e2, 0x03f4, 0x0401, 0x040d, 0x040e, 0x0450, 0x0451, 0x045d, 0x045e, 0x0482, 0x0490, 0x04c5, 0x04c7, 0x04c9, + 0x04cb, 0x04cd, 0x04d0, 0x04ec, 0x04ee, 0x04f6, 0x04f8, 0x04fa, 0x0531, 0x0557, 0x0559, 0x055a, 0x0561, 0x0587, 0x05d0, 0x05eb, + 0x05f0, 0x05f3, 0x0621, 0x063b, 0x0641, 0x064b, 0x0660, 0x066a, 0x0671, 0x06b8, 0x06ba, 0x06bf, 0x06c0, 0x06cf, 0x06d0, 0x06d4, + 0x06d5, 0x06d6, 0x06e5, 0x06e7, 0x06f0, 0x06fa, 0x0905, 0x093a, 0x093d, 0x093e, 0x0958, 0x0962, 0x0966, 0x0970, 0x0985, 0x098d, + 0x098f, 0x0991, 0x0993, 0x09a9, 0x09aa, 0x09b1, 0x09b2, 0x09b3, 0x09b6, 0x09ba, 0x09dc, 0x09de, 0x09df, 0x09e2, 0x09e6, 0x09f2, + 0x0a05, 0x0a0b, 0x0a0f, 0x0a11, 0x0a13, 0x0a29, 0x0a2a, 0x0a31, 0x0a32, 0x0a34, 0x0a35, 0x0a37, 0x0a38, 0x0a3a, 0x0a59, 0x0a5d, + 0x0a5e, 0x0a5f, 0x0a66, 0x0a70, 0x0a72, 0x0a75, 0x0a85, 0x0a8c, 0x0a8d, 0x0a8e, 0x0a8f, 0x0a92, 0x0a93, 0x0aa9, 0x0aaa, 0x0ab1, + 0x0ab2, 0x0ab4, 0x0ab5, 0x0aba, 0x0abd, 0x0abe, 0x0ae0, 0x0ae1, 0x0ae6, 0x0af0, 0x0b05, 0x0b0d, 0x0b0f, 0x0b11, 0x0b13, 0x0b29, + 0x0b2a, 0x0b31, 0x0b32, 0x0b34, 0x0b36, 0x0b3a, 0x0b3d, 0x0b3e, 0x0b5c, 0x0b5e, 0x0b5f, 0x0b62, 0x0b66, 0x0b70, 0x0b85, 0x0b8b, + 0x0b8e, 0x0b91, 0x0b92, 0x0b96, 0x0b99, 0x0b9b, 0x0b9c, 0x0b9d, 0x0b9e, 0x0ba0, 0x0ba3, 0x0ba5, 0x0ba8, 0x0bab, 0x0bae, 0x0bb6, + 0x0bb7, 0x0bba, 0x0be7, 0x0bf0, 0x0c05, 0x0c0d, 0x0c0e, 0x0c11, 0x0c12, 0x0c29, 0x0c2a, 0x0c34, 0x0c35, 0x0c3a, 0x0c60, 0x0c62, + 0x0c66, 0x0c70, 0x0c85, 0x0c8d, 0x0c8e, 0x0c91, 0x0c92, 0x0ca9, 0x0caa, 0x0cb4, 0x0cb5, 0x0cba, 0x0cde, 0x0cdf, 0x0ce0, 0x0ce2, + 0x0ce6, 0x0cf0, 0x0d05, 0x0d0d, 0x0d0e, 0x0d11, 0x0d12, 0x0d29, 0x0d2a, 0x0d3a, 0x0d60, 0x0d62, 0x0d66, 0x0d70, 0x0e01, 0x0e2f, + 0x0e30, 0x0e31, 0x0e32, 0x0e34, 0x0e40, 0x0e46, 0x0e50, 0x0e5a, 0x0e81, 0x0e83, 0x0e84, 0x0e85, 0x0e87, 0x0e89, 0x0e8a, 0x0e8b, + 0x0e8d, 0x0e8e, 0x0e94, 0x0e98, 0x0e99, 0x0ea0, 0x0ea1, 0x0ea4, 0x0ea5, 0x0ea6, 0x0ea7, 0x0ea8, 0x0eaa, 0x0eac, 0x0ead, 0x0eaf, + 0x0eb0, 0x0eb1, 0x0eb2, 0x0eb4, 0x0ebd, 0x0ebe, 0x0ec0, 0x0ec5, 0x0ed0, 0x0eda, 0x0f20, 0x0f2a, 0x0f40, 0x0f48, 0x0f49, 0x0f6a, + 0x10a0, 0x10c6, 0x10d0, 0x10f7, 0x1100, 0x1101, 0x1102, 0x1104, 0x1105, 0x1108, 0x1109, 0x110a, 0x110b, 0x110d, 0x110e, 0x1113, + 0x113c, 0x113d, 0x113e, 0x113f, 0x1140, 0x1141, 0x114c, 0x114d, 0x114e, 0x114f, 0x1150, 0x1151, 0x1154, 0x1156, 0x1159, 0x115a, + 0x115f, 0x1162, 0x1163, 0x1164, 0x1165, 0x1166, 0x1167, 0x1168, 0x1169, 0x116a, 0x116d, 0x116f, 0x1172, 0x1174, 0x1175, 0x1176, + 0x119e, 0x119f, 0x11a8, 0x11a9, 0x11ab, 0x11ac, 0x11ae, 0x11b0, 0x11b7, 0x11b9, 0x11ba, 0x11bb, 0x11bc, 0x11c3, 0x11eb, 0x11ec, + 0x11f0, 0x11f1, 0x11f9, 0x11fa, 0x1e00, 0x1e9c, 0x1ea0, 0x1efa, 0x1f00, 0x1f16, 0x1f18, 0x1f1e, 0x1f20, 0x1f46, 0x1f48, 0x1f4e, + 0x1f50, 0x1f58, 0x1f59, 0x1f5a, 0x1f5b, 0x1f5c, 0x1f5d, 0x1f5e, 0x1f5f, 0x1f7e, 0x1f80, 0x1fb5, 0x1fb6, 0x1fbd, 0x1fbe, 0x1fbf, + 0x1fc2, 0x1fc5, 0x1fc6, 0x1fcd, 0x1fd0, 0x1fd4, 0x1fd6, 0x1fdc, 0x1fe0, 0x1fed, 0x1ff2, 0x1ff5, 0x1ff6, 0x1ffd, 0x2126, 0x2127, + 0x212a, 0x212c, 0x212e, 0x212f, 0x2180, 0x2183, 0x3007, 0x3008, 0x3021, 0x302a, 0x3041, 0x3095, 0x30a1, 0x30fb, 0x3105, 0x312d, + 0x4e00, 0x9fa6, 0xac00, 0xd7a4 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLLetterOrDigit((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLLetterOrDigit but it failed."); + } + } else { + if (Verifier.isXMLLetterOrDigit((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLLetterOrDigit but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLLetterOrDigit in %dms\n", System.currentTimeMillis() - ms); + } + + + // Automated test built by VerifierTestBuilder + @Test + public void testIsXMLWhitespace () { + final int[] flips = new int[] { + + 0x0009, 0x000b, 0x000d, 0x000e, 0x0020, 0x0021 + }; + int c = 0; + int fcnt = 0; + boolean valid = false; + final long ms = System.currentTimeMillis(); + while (c <= Character.MAX_VALUE) { + if (fcnt < flips.length && flips[fcnt] == c) { + valid = !valid; + fcnt++; + } + if (valid) { + if (!Verifier.isXMLWhitespace((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to pass isXMLWhitespace but it failed."); + } + } else { + if (Verifier.isXMLWhitespace((char)c)) { + fail("Expected char 0x" + Integer.toHexString(c) + " to fail isXMLWhitespace but it passed."); + } + } + c++; + } + System.out.printf("Completed test testIsXMLWhitespace in %dms\n", System.currentTimeMillis() - ms); + } + +} diff --git a/test/src/java/org/jdom/test/cases/adapters/TestJAXPDOMAdapter.java b/test/src/java/org/jdom/test/cases/adapters/TestJAXPDOMAdapter.java new file mode 100644 index 0000000..6d68f89 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/adapters/TestJAXPDOMAdapter.java @@ -0,0 +1,31 @@ +package org.jdom.test.cases.adapters; + +import org.junit.Test; +import org.w3c.dom.Document; + +import org.jdom.JDOMException; +import org.jdom.adapters.JAXPDOMAdapter; + +@SuppressWarnings("javadoc") +public class TestJAXPDOMAdapter { + + @Test + public void testCreateDocument() throws JDOMException { + for (int i = 10; i > 0; i--) { + timeDoc(); + } + } + + private void timeDoc() throws JDOMException { + long time = System.nanoTime(); + long hash = 0L; + final int cnt = 1000; + for (int i = 0; i < cnt; i++) { + Document doc = new JAXPDOMAdapter().createDocument(); + hash += doc.hashCode(); + } + time = System.nanoTime() - time; + System.out.printf("JAXPDOMAdapter Speed %.3f %d\n", (time / 1000000.0) / cnt, hash & 0x01); + } + +} diff --git a/test/src/java/org/jdom/test/cases/filter/AbstractTestFilter.java b/test/src/java/org/jdom/test/cases/filter/AbstractTestFilter.java new file mode 100644 index 0000000..f67cf78 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/filter/AbstractTestFilter.java @@ -0,0 +1,480 @@ +package org.jdom.test.cases.filter; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.RandomAccess; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter2.*; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class AbstractTestFilter { + + protected static final void assertFilterNotEquals(Filter a, Filter b) { + assertTrue("A Filter is null.", a != null); + assertTrue("B Filter is null.", b != null); + assertTrue (!a.equals(null)); + assertTrue (!b.equals(null)); + if (a.equals(b) || b.equals(a)) { + fail("Filters are equals(), but they are not supposed to be: " + + a.toString() + " and " + b.toString()); + } + if (a.hashCode() == b.hashCode()) { + System.out.println("Two different (not equals() ) Filters have " + + "the same hashCode(): " + a.hashCode() + "\n " + + a.toString() + " \n " + b.toString()); + } + Filter base = new ContentFilter(); + assertFalse(base.refine(a).equals(base.refine(b))); + } + + protected static final void assertFilterEquals(Filter a, Filter b) { + assertTrue("A Filter is null.", a != null); + assertTrue("B Filter is null.", b != null); + assertTrue (!a.equals(null)); + assertTrue (!b.equals(null)); + if (!a.equals(a)) { + fail("Filter " + a.toString() + " is not equals() to itself"); + } + if (!b.equals(b)) { + fail("Filter " + b.toString() + " is not equals() to itself"); + } + if (!a.equals(b)) { + fail("Filters are not equals(), but they are supposed to be: " + + a.toString() + " and " + b.toString()); + } + if (!b.equals(a)) { + fail("Filters a.equals(b), but not b.equals(a) : " + + a.toString() + " and " + b.toString()); + } + if (a.hashCode() != b.hashCode()) { + fail("Both filters are equals(), but their hashCode() values differ: " + + a.toString() + " and " + b.toString()); + } + Filter base = new ContentFilter(); + assertTrue(base.refine(a).equals(base.refine(b))); + } + + protected interface CallBack { + boolean isValid(Object c); + } + + protected class NegateCallBack implements CallBack { + private final CallBack basecallback; + public NegateCallBack(CallBack base) { + basecallback = base; + } + @Override + public boolean isValid(Object c) { + return !basecallback.isValid(c); + } + } + + protected class AndCallBack implements CallBack { + private final CallBack onecallback, twocallback; + public AndCallBack(CallBack one, CallBack two) { + onecallback = one; + twocallback = two; + } + @Override + public boolean isValid(Object c) { + // do not want to do short-circuit || logic. + // Make seperate statements + boolean one = onecallback.isValid(c); + boolean two = twocallback.isValid(c); + return one && two; + } + } + + protected class OrCallBack implements CallBack { + private final CallBack onecallback, twocallback; + public OrCallBack(CallBack one, CallBack two) { + onecallback = one; + twocallback = two; + } + @Override + public boolean isValid(Object c) { + // do not want to do short-circuit || logic. + // Make seperate statements + boolean one = onecallback.isValid(c); + boolean two = twocallback.isValid(c); + return one || two; + } + } + + private class TrueCallBack implements CallBack { + @Override + public boolean isValid(Object c) { + return true; + } + } + + private class FalseCallBack implements CallBack { + @Override + public boolean isValid(Object c) { + return false; + } + } + + private final Document doc; + private final Element root; + private final Namespace testns; + private final Content[] rootcontent; + private final Content[] doccontent; + + protected AbstractTestFilter() { + + root = new Element("root"); + testns = Namespace.getNamespace("testns", "http://jdom.org/testns"); + + Element zero = new Element("zero"); + Element one = new Element("one"); + Element two = new Element("two"); + Element three = new Element("three", testns); + Element four = new Element("four"); + Element five = new Element("five", testns); + Element six = new Element("six"); + Element seven = new Element("seven"); + + EntityRef e0 = new EntityRef("erent", "ERSystemID"); + + Text t0 = new Text("t0"); + Text t1 = new Text("t1"); + Text t2 = new Text("t2"); + Text t3 = new Text("t3"); + Text t4 = new Text("t4"); + Text t5 = new Text("t5"); + Text t6 = new Text("t6"); + Text t7 = new Text("t7"); + Text t8 = new Text("t8"); + Text t9 = new Text("t9"); + + CDATA c0 = new CDATA("c0"); + CDATA c1 = new CDATA("c1"); + + Comment com0 = new Comment("Comment0"); + Comment com1 = new Comment("Comment1"); + Comment com2 = new Comment("Comment2"); + + DocType doctype = new DocType("root"); + ProcessingInstruction pi = new ProcessingInstruction ("dummy", "name=value"); + Comment doccom = new Comment("DocComment"); + doc = new Document(); + + + doc.addContent(doctype); + doc.addContent(pi); + doc.addContent(doccom); + doc.addContent(root); + + doccontent = new Content[] {doctype, pi, doccom, root}; + + rootcontent = new Content[] {t0, com0, t1, zero, t2, one, t3, com1, + t4, two, t5, three, c0, four, t6, e0, five, t7, six, c1, t8, + seven, t9, com2}; + + for (Content c : rootcontent ) { + root.addContent(c); + } + + } + + protected Document getDocument() { + return doc; + } + + protected Element getRoot() { + return root; + } + + protected Content[] getDocumentContent() { + return doccontent; + } + + protected Content[] getRootContent() { + return rootcontent; + } + + protected Namespace getTestNamespace() { + return testns; + } + + protected Content[] filter(Content[] input, Class...types) { + ArrayList al = new ArrayList(input.length); + content: for (Content c : input) { + for (Class cclass : types) { + if (cclass.isInstance(c)) { + al.add(c); + continue content; + } + } + } + return al.toArray(new Content[al.size()]); + } + + + protected void exercise(Filter af, Parent parent, CallBack callback) { + assertTrue("filter is null", af != null); + assertTrue("list is null", parent != null); + assertTrue("callback is null", callback != null); + assertTrue(af.toString() != null); // basic test to ensure toString is run. + // can never match null if it returns a + assertFalse(af.matches(null)); + // can never match Object if it returns a + assertFalse(af.matches(new Object())); + exerciseCore(af, parent, callback); + + // test the deserialized version of the Filter. + final Filter des = UnitTestUtil.deSerialize(af); + assertTrue(des != af); + assertFilterEquals(af, des); + exerciseCore(des, parent, callback); + + try { + Filter or = af.or(null); + fail ("expected an exception from " + or); + } catch (RuntimeException re) { + // good + } catch (Exception e) { + fail ("Expected a RuntimeException."); + } + + try { + Filter and = af.and(null); + fail ("expected an exception from " + and); + } catch (RuntimeException re) { + // good + } catch (Exception e) { + fail ("Expected a RuntimeException."); + } + + exerciseCore(af.negate().refine(Filters.content()), parent, new NegateCallBack(callback)); + exerciseCore(af.or(af.negate()).refine(Filters.content()), parent, new TrueCallBack()); + exerciseCore(af.or(UnitTestUtil.deSerialize(af)).refine(Filters.content()), parent, callback); + exerciseCore(af.negate().and(af).refine(Filters.content()), parent, new FalseCallBack()); + exerciseCore(af.and(af).refine(Filters.content()), parent, callback); + exerciseCore(af.and(UnitTestUtil.deSerialize(af)).refine(Filters.content()), parent, callback); + + Filter nf = af.negate(); + exerciseCore(nf.negate().refine(Filters.content()), parent, callback); + exerciseCore(nf.or(nf.negate()).refine(Filters.content()), parent, new TrueCallBack()); + exerciseCore(nf.and(nf.negate()).refine(Filters.content()), parent, new FalseCallBack()); + + + Filter afor = UnitTestUtil.deSerialize(af).or(nf); + Filter bfor = nf.or(af); + assertFilterEquals(afor, bfor); + + Filter afand = UnitTestUtil.deSerialize(af).and(nf); + Filter bfand = nf.and(af); + assertFilterEquals(afand, bfand); + + assertFalse(af.equals(null)); + assertFalse(nf.equals(null)); + assertFalse(afor.equals(null)); + assertFalse(bfor.equals(null)); + assertFalse(afand.equals(null)); + assertFalse(bfand.equals(null)); + + } + + private final void exerciseCore(Filter ef, Parent parent, CallBack callback) { + // exercise the toString() + assertTrue(ef.toString() != null); + LinkedList oc = new LinkedList(); + ArrayList mc = new ArrayList(); + List cont = parent.getContent(); + for (Content c : cont) { + oc.add(c); + assertTrue(parent == c.getParent()); + if (parent instanceof Document) { + assertTrue(null == c.getParentElement()); + } else { + assertTrue(parent == c.getParentElement()); + } + boolean mat = ef.matches(c); + if (mat) { + mc.add(c); + } + boolean cbv = callback.isValid(c); + if (mat != cbv) { + fail ("Filter " + ef + " returned " + mat + + " but isValid CallBack returned " + cbv + + " for value " + c); + } + } + List fc = ef.filter(oc); + assertTrue(fc instanceof RandomAccess); + assertTrue(fc.size() == mc.size()); + for (int i = 0; i < fc.size(); i++) { + assertTrue(fc.get(i) == mc.get(i)); + } + Filter cf = UnitTestUtil.deSerialize(ef); + assertFilterEquals(cf, ef); + ContentFilter xf = new ContentFilter(); + assertFilterEquals(cf.refine(xf), ef.refine(xf)); + assertFilterEquals(xf.refine(cf), xf.refine(ef)); + assertFalse(ef.equals(null)); + assertFilterNotEquals(ef, ef.negate()); + assertFilterNotEquals(ef.refine(xf), ef.negate().refine(xf)); + assertFilterNotEquals(xf.refine(ef), xf.refine(ef.negate())); + List depth = new ArrayList(); + depth.addAll(cont); + int sz = depth.size(); + for (int i = 0; i < sz; i++) { + if (depth.get(i) instanceof Element) { + List kdata = ((Element)depth.get(i)).getContent(); + depth.addAll(i+1, kdata); + sz += kdata.size(); + } + } + Iterator di = parent.getDescendants(); + + // confirm that the DepthIterator iterates over all content. + UnitTestUtil.testReadIterator(di, depth.toArray()); + + List filtered = new ArrayList(depth.size()); + for (Iterator it = depth.iterator(); it.hasNext(); ) { + Content c = it.next(); + if (callback.isValid(c)) { + filtered.add(c); + } + } + + // + di = parent.getDescendants(AbstractFilter.toFilter(ef)); + UnitTestUtil.testReadIterator(di, filtered.toArray()); + assertEquals(filtered, ef.filter(depth)); + } + + + + + protected void exerciseAtt(Filter af, Parent parent, CallBack callback) { + assertTrue("filter is null", af != null); + assertTrue("list is null", parent != null); + assertTrue("callback is null", callback != null); + assertTrue(af.toString() != null); // basic test to ensure toString is run. + // can never match null if it returns a + assertFalse(af.matches(null)); + // can never match Object if it returns a + assertFalse(af.matches(new Object())); + exerciseCoreAtt(af, parent, callback); + try { + Filter or = af.or(null); + fail ("expected an exception from " + or); + } catch (RuntimeException re) { + // good + } catch (Exception e) { + fail ("Expected a RuntimeException."); + } + + try { + Filter and = af.and(null); + fail ("expected an exception from " + and); + } catch (RuntimeException re) { + // good + } catch (Exception e) { + fail ("Expected a RuntimeException."); + } + + //exerciseCoreAtt(af.negate().refine(Filters.attribute()), parent, new NegateCallBack(callback)); + //exerciseCoreAtt(af.or(af.negate()).refine(Filters.attribute()), parent, new TrueCallBack()); + exerciseCoreAtt(af.or(UnitTestUtil.deSerialize(af)).refine(Filters.attribute()), parent, callback); + //exerciseCoreAtt(af.negate().and(af).refine(Filters.attribute()), parent, new FalseCallBack()); + exerciseCoreAtt(af.and(af).refine(Filters.attribute()), parent, callback); + exerciseCoreAtt(af.and(UnitTestUtil.deSerialize(af)).refine(Filters.attribute()), parent, callback); + + Filter nf = af.negate(); +// exerciseCore(nf.negate().refine(Filters.content()), parent, callback); +// exerciseCore(nf.or(nf.negate()).refine(Filters.content()), parent, new TrueCallBack()); +// exerciseCore(nf.and(nf.negate()).refine(Filters.content()), parent, new FalseCallBack()); + + + Filter afor = UnitTestUtil.deSerialize(af).or(nf); + Filter bfor = nf.or(af); + assertFilterEquals(afor, bfor); + + Filter afand = UnitTestUtil.deSerialize(af).and(nf); + Filter bfand = nf.and(af); + assertFilterEquals(afand, bfand); + + assertFalse(af.equals(null)); + assertFalse(nf.equals(null)); + assertFalse(afor.equals(null)); + assertFalse(bfor.equals(null)); + assertFalse(afand.equals(null)); + assertFalse(bfand.equals(null)); + + } + + private final void exerciseCoreAtt(Filter ef, Parent parent, CallBack callback) { + // exercise the toString() + assertTrue(ef.toString() != null); + List cont = parent.getContent(); + for (Content c : cont) { + assertTrue(parent == c.getParent()); + if (parent instanceof Document) { + assertTrue(null == c.getParentElement()); + } else { + assertTrue(parent == c.getParentElement()); + } + boolean mat = ef.matches(c); + boolean cbv = callback.isValid(c); + if (mat != cbv) { + fail ("Filter " + ef + " returned " + mat + + " but isValid CallBack returned " + cbv + + " for value " + c); + } + } + Filter cf = UnitTestUtil.deSerialize(ef); + assertFilterEquals(cf, ef); + AttributeFilter xf = new AttributeFilter(); + assertFilterEquals(cf.refine(xf), ef.refine(xf)); + assertFilterEquals(xf.refine(cf), xf.refine(ef)); + assertFalse(ef.equals(null)); + assertFilterNotEquals(ef, ef.negate()); + assertFilterNotEquals(ef.refine(xf), ef.negate().refine(xf)); + assertFilterNotEquals(xf.refine(ef), xf.refine(ef.negate())); + + List cnt = new ArrayList(); + List atts = new ArrayList(); + for (Iterator itc = parent.getDescendants(); itc.hasNext();) { + Content c = itc.next(); + cnt.add(c); + if (c instanceof Element) { + cnt.addAll(((Element)c).getAttributes()); + for (Attribute a : ((Element)c).getAttributes()) { + if (ef.matches(a)) { + atts.add(a); + } + } + } + } + + List filtered = ef.filter(cnt); + + assertEquals(atts, filtered); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/filter/TestAtributeFilter.java b/test/src/java/org/jdom/test/cases/filter/TestAtributeFilter.java new file mode 100644 index 0000000..3c5be6b --- /dev/null +++ b/test/src/java/org/jdom/test/cases/filter/TestAtributeFilter.java @@ -0,0 +1,160 @@ +package org.jdom.test.cases.filter; + +import org.jdom.Attribute; +import org.jdom.Namespace; +import org.jdom.filter2.AttributeFilter; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestAtributeFilter extends AbstractTestFilter { + + @Test + public void testAttributeFilter() { + AttributeFilter ef = new AttributeFilter(); + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c != null && c instanceof Attribute; + } + }; + exerciseAtt(ef, getRoot(), cb); + exerciseAtt(ef, getDocument(), cb); + } + + @Test + public void testElementFilterString() { + final String name = "four"; + AttributeFilter ef = new AttributeFilter(name); + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Attribute) && + name.equals(((Attribute)c).getName()); + } + }; + exerciseAtt(ef, getRoot(), cb); + exerciseAtt(ef, getDocument(), cb); + } + + @Test + public void testElementFilterNamespace() { + AttributeFilter efa = new AttributeFilter(Namespace.NO_NAMESPACE); + CallBack cba = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Attribute) && + Namespace.NO_NAMESPACE.equals(((Attribute)c).getNamespace()); + } + }; + exerciseAtt(efa, getRoot(), cba); + exerciseAtt(efa, getDocument(), cba); + + AttributeFilter efb = new AttributeFilter(getTestNamespace()); + CallBack cbb = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Attribute) && + getTestNamespace().equals(((Attribute)c).getNamespace()); + } + }; + exerciseAtt(efb, getRoot(), cbb); + exerciseAtt(efb, getDocument(), cbb); + + + } + + @Test + public void testElementFilterStringNamespace() { + final String namea = "four"; + AttributeFilter efa = new AttributeFilter(namea, Namespace.NO_NAMESPACE); + CallBack cba = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Attribute) && + namea.equals(((Attribute)c).getName()) && + Namespace.NO_NAMESPACE.equals(((Attribute)c).getNamespace()); + } + }; + exerciseAtt(efa, getRoot(), cba); + exerciseAtt(efa, getDocument(), cba); + + final String nameb = "three"; + AttributeFilter efb = new AttributeFilter(nameb, getTestNamespace()); + CallBack cbb = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Attribute) && + nameb.equals(((Attribute)c).getName()) && + getTestNamespace().equals(((Attribute)c).getNamespace()); + } + }; + exerciseAtt(efb, getRoot(), cbb); + exerciseAtt(efb, getDocument(), cbb); + + } + + @Test + public void testEqualsObject() { + final String URI1 = "http://jdom.org/test1"; + final String URI2 = "http://jdom.org/test2"; + Namespace ns1a = Namespace.getNamespace("pfxa", URI1); + Namespace ns1b = Namespace.getNamespace("pfxb", URI1); + Namespace ns1c = Namespace.getNamespace(URI1); + + Namespace ns2a = Namespace.getNamespace("pfxa", URI2); + Namespace ns2c = Namespace.getNamespace(URI2); + + assertFilterEquals(new AttributeFilter("test"), + new AttributeFilter("test")); + + // same namespace + assertFilterEquals(new AttributeFilter("test", ns1a), + new AttributeFilter("test", ns1a)); + + // same namespace URI (different prefix) + assertFilterEquals(new AttributeFilter("test", ns1a), + new AttributeFilter("test", ns1b)); + + // same namespace URI (different prefix) + assertFilterEquals(new AttributeFilter("test", ns1a), + new AttributeFilter("test", ns1c)); + + // same namespace URI (different prefix) + assertFilterEquals(new AttributeFilter(ns1a), + new AttributeFilter(ns1a)); + + // same namespace URI (different prefix) + assertFilterEquals(new AttributeFilter(ns1a), + new AttributeFilter(ns1b)); + + // same namespace URI (different prefix) + assertFilterEquals(new AttributeFilter(ns1a), + new AttributeFilter(ns1c)); + + assertFilterNotEquals(new AttributeFilter("test", ns1a), + new AttributeFilter(ns1a)); + + assertFilterNotEquals(new AttributeFilter("test"), + new AttributeFilter("testfoo")); + + assertFilterNotEquals(new AttributeFilter("test"), + new AttributeFilter("test", Namespace.NO_NAMESPACE)); + + assertFilterNotEquals(new AttributeFilter("test", ns1a), + new AttributeFilter("test", Namespace.NO_NAMESPACE)); + + assertFilterNotEquals(new AttributeFilter("test", Namespace.NO_NAMESPACE), + new AttributeFilter("test", ns1a)); + + assertFilterNotEquals(new AttributeFilter(ns1a), + new AttributeFilter(ns2a)); + + assertFilterNotEquals(new AttributeFilter(ns1c), + new AttributeFilter(ns2c)); + + assertFilterNotEquals(new AttributeFilter(ns1c), + new AttributeFilter(ns1c).negate()); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/filter/TestContentFilter.java b/test/src/java/org/jdom/test/cases/filter/TestContentFilter.java new file mode 100644 index 0000000..a9213ef --- /dev/null +++ b/test/src/java/org/jdom/test/cases/filter/TestContentFilter.java @@ -0,0 +1,282 @@ +package org.jdom.test.cases.filter; + +import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; + +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DocType; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter2.ContentFilter; +import org.jdom.filter2.ElementFilter; +import org.jdom.test.util.UnitTestUtil; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestContentFilter extends AbstractTestFilter { + + private final int[] allContent = new int[] { + ContentFilter.CDATA, ContentFilter.COMMENT, + ContentFilter.DOCTYPE, ContentFilter.DOCUMENT, + ContentFilter.ELEMENT, ContentFilter.ENTITYREF, + ContentFilter.PI, ContentFilter.TEXT + }; + + @Test + public void testSetters() { + int mask = 0; + boolean flag = false; + ContentFilter cfa = new ContentFilter(false); + do { + flag = !flag; + for (int m : allContent) { + switch (m) { + case ContentFilter.CDATA : + cfa.setCDATAVisible(flag); + break; + case ContentFilter.COMMENT : + cfa.setCommentVisible(flag); + break; + case ContentFilter.DOCTYPE : + cfa.setDocTypeVisible(flag); + break; + case ContentFilter.DOCUMENT : + if (flag) { + cfa.setFilterMask(cfa.getFilterMask() | ContentFilter.DOCUMENT); + } else { + cfa.setFilterMask(cfa.getFilterMask() & (~ ContentFilter.DOCUMENT) ); + } + break; + case ContentFilter.ELEMENT : + cfa.setElementVisible(flag); + break; + case ContentFilter.ENTITYREF : + cfa.setEntityRefVisible(flag); + break; + case ContentFilter.PI : + cfa.setPIVisible(flag); + break; + case ContentFilter.TEXT : + cfa.setTextVisible(flag); + break; + } + if (flag) { + mask |= m; + } else { + mask &= (~m); + } + if (cfa.getFilterMask() != mask) { + fail(String.format("ContentFilter Mask is out of sync after " + + "setting flag %d with value %s", m, flag)); + } + } + } while (flag); + } + + @Test + public void testDefaultDocumentContent() { + ContentFilter cf = new ContentFilter(); + cf.setDocumentContent(); + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + if (c instanceof Element) { + return true; + } + if (c instanceof ProcessingInstruction) { + return true; + } + if (c instanceof DocType) { + return true; + } + if (c instanceof Comment) { + return true; + } + return false; + } + }; + + exerciseContent(cb, ContentFilter.PI, ContentFilter.ELEMENT, + ContentFilter.COMMENT, ContentFilter.DOCTYPE); + + } + + @Test + public void testAllElementContent() { + ContentFilter cf = new ContentFilter(); + cf.setElementContent(); + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + if (c instanceof Element) { + return true; + } + if (c instanceof ProcessingInstruction) { + return true; + } + if (c instanceof EntityRef) { + return true; + } + if (c instanceof CDATA) { + return true; + } + if (c instanceof Text) { + return true; + } + if (c instanceof Comment) { + return true; + } + return false; + } + }; + + exerciseContent(cb, ContentFilter.ELEMENT, ContentFilter.ENTITYREF, + ContentFilter.COMMENT, ContentFilter.CDATA, + ContentFilter.TEXT, ContentFilter.PI); + + } + + + @Test + public void testAllContentFilter() { + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c != null; + } + }; + exerciseContent(cb, ContentFilter.CDATA, ContentFilter.COMMENT, + ContentFilter.DOCTYPE, ContentFilter.DOCUMENT, + ContentFilter.ELEMENT, ContentFilter.ENTITYREF, + ContentFilter.PI, ContentFilter.TEXT); + } + + @Test + public void testElementContentFilter() { + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c instanceof Element; + } + }; + exerciseContent(cb, ContentFilter.ELEMENT); + } + +// @Test +// public void testDocumentContentFilter() { +// ContentFilter cf = new ContentFilter(ContentFilter.DOCUMENT); +// assertTrue(cf.matches(getDocument())); +// assertFalse(cf.matches(getRoot())); +// ContentFilter cfe = new ContentFilter(ContentFilter.ELEMENT); +// assertFalse(cfe.matches(getDocument())); +// } + + @Test + public void testDocTypeContentFilter() { + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c instanceof DocType; + } + }; + exerciseContent(cb, ContentFilter.DOCTYPE); + } + + @Test + public void testPIContentFilter() { + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c instanceof ProcessingInstruction; + } + }; + exerciseContent(cb, ContentFilter.PI); + } + + @Test + public void testCDATAContentFilter() { + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c instanceof CDATA; + } + }; + exerciseContent(cb, ContentFilter.CDATA); + } + + @Test + public void testCommentContentFilter() { + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c instanceof Comment; + } + }; + exerciseContent(cb, ContentFilter.COMMENT); + } + + private void exerciseContent(CallBack cb, int...types) { + int mask = 0; + ContentFilter cfa = new ContentFilter(0); + for (int m : types) { + mask |= m; + switch (m) { + case ContentFilter.CDATA : + cfa.setCDATAVisible(true); + break; + case ContentFilter.COMMENT : + cfa.setCommentVisible(true); + break; + case ContentFilter.DOCTYPE : + cfa.setDocTypeVisible(true); + break; + case ContentFilter.DOCUMENT : + cfa.setFilterMask(cfa.getFilterMask() | ContentFilter.DOCUMENT); + break; + case ContentFilter.ELEMENT : + cfa.setElementVisible(true); + break; + case ContentFilter.ENTITYREF : + cfa.setEntityRefVisible(true); + break; + case ContentFilter.PI : + cfa.setPIVisible(true); + break; + case ContentFilter.TEXT : + cfa.setTextVisible(true); + break; + } + } + exercise(cfa, getRoot(), cb); + exercise(cfa, getDocument(), cb); + + ContentFilter cf = new ContentFilter(mask); + exercise(cf, getRoot(), cb); + exercise(cf, getDocument(), cb); + + assertFilterEquals(cf, cfa); + + assertTrue(cf.filter(new Object()) == null); + assertTrue(cfa.filter(new Object()) == null); + } + + @Test + public void testEqualsObject() { + assertFilterEquals(new ContentFilter(), new ContentFilter(true)); + + ContentFilter cfa = new ContentFilter(ContentFilter.CDATA); + ContentFilter cfb = UnitTestUtil.deSerialize(cfa); + assertFilterEquals(cfa, cfb); + cfa.setCommentVisible(true); + assertFilterNotEquals(cfa, cfb); + + assertFilterNotEquals(cfa, new ContentFilter(true)); + assertFilterNotEquals(cfa, new ContentFilter(false)); + assertFilterNotEquals(cfa, new ElementFilter()); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/filter/TestElementFilter.java b/test/src/java/org/jdom/test/cases/filter/TestElementFilter.java new file mode 100644 index 0000000..402384e --- /dev/null +++ b/test/src/java/org/jdom/test/cases/filter/TestElementFilter.java @@ -0,0 +1,160 @@ +package org.jdom.test.cases.filter; + +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.filter2.ElementFilter; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestElementFilter extends AbstractTestFilter { + + @Test + public void testElementFilter() { + ElementFilter ef = new ElementFilter(); + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return c != null && c instanceof Element; + } + }; + exercise(ef, getRoot(), cb); + exercise(ef, getDocument(), cb); + } + + @Test + public void testElementFilterString() { + final String name = "four"; + ElementFilter ef = new ElementFilter(name); + CallBack cb = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Element) && + name.equals(((Element)c).getName()); + } + }; + exercise(ef, getRoot(), cb); + exercise(ef, getDocument(), cb); + } + + @Test + public void testElementFilterNamespace() { + ElementFilter efa = new ElementFilter(Namespace.NO_NAMESPACE); + CallBack cba = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Element) && + Namespace.NO_NAMESPACE.equals(((Element)c).getNamespace()); + } + }; + exercise(efa, getRoot(), cba); + exercise(efa, getDocument(), cba); + + ElementFilter efb = new ElementFilter(getTestNamespace()); + CallBack cbb = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Element) && + getTestNamespace().equals(((Element)c).getNamespace()); + } + }; + exercise(efb, getRoot(), cbb); + exercise(efb, getDocument(), cbb); + + + } + + @Test + public void testElementFilterStringNamespace() { + final String namea = "four"; + ElementFilter efa = new ElementFilter(namea, Namespace.NO_NAMESPACE); + CallBack cba = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Element) && + namea.equals(((Element)c).getName()) && + Namespace.NO_NAMESPACE.equals(((Element)c).getNamespace()); + } + }; + exercise(efa, getRoot(), cba); + exercise(efa, getDocument(), cba); + + final String nameb = "three"; + ElementFilter efb = new ElementFilter(nameb, getTestNamespace()); + CallBack cbb = new CallBack() { + @Override + public boolean isValid(Object c) { + return (c != null) && (c instanceof Element) && + nameb.equals(((Element)c).getName()) && + getTestNamespace().equals(((Element)c).getNamespace()); + } + }; + exercise(efb, getRoot(), cbb); + exercise(efb, getDocument(), cbb); + + } + + @Test + public void testEqualsObject() { + final String URI1 = "http://jdom.org/test1"; + final String URI2 = "http://jdom.org/test2"; + Namespace ns1a = Namespace.getNamespace("pfxa", URI1); + Namespace ns1b = Namespace.getNamespace("pfxb", URI1); + Namespace ns1c = Namespace.getNamespace(URI1); + + Namespace ns2a = Namespace.getNamespace("pfxa", URI2); + Namespace ns2c = Namespace.getNamespace(URI2); + + assertFilterEquals(new ElementFilter("test"), + new ElementFilter("test")); + + // same namespace + assertFilterEquals(new ElementFilter("test", ns1a), + new ElementFilter("test", ns1a)); + + // same namespace URI (different prefix) + assertFilterEquals(new ElementFilter("test", ns1a), + new ElementFilter("test", ns1b)); + + // same namespace URI (different prefix) + assertFilterEquals(new ElementFilter("test", ns1a), + new ElementFilter("test", ns1c)); + + // same namespace URI (different prefix) + assertFilterEquals(new ElementFilter(ns1a), + new ElementFilter(ns1a)); + + // same namespace URI (different prefix) + assertFilterEquals(new ElementFilter(ns1a), + new ElementFilter(ns1b)); + + // same namespace URI (different prefix) + assertFilterEquals(new ElementFilter(ns1a), + new ElementFilter(ns1c)); + + assertFilterNotEquals(new ElementFilter("test", ns1a), + new ElementFilter(ns1a)); + + assertFilterNotEquals(new ElementFilter("test"), + new ElementFilter("testfoo")); + + assertFilterNotEquals(new ElementFilter("test"), + new ElementFilter("test", Namespace.NO_NAMESPACE)); + + assertFilterNotEquals(new ElementFilter("test", ns1a), + new ElementFilter("test", Namespace.NO_NAMESPACE)); + + assertFilterNotEquals(new ElementFilter("test", Namespace.NO_NAMESPACE), + new ElementFilter("test", ns1a)); + + assertFilterNotEquals(new ElementFilter(ns1a), + new ElementFilter(ns2a)); + + assertFilterNotEquals(new ElementFilter(ns1c), + new ElementFilter(ns2c)); + + assertFilterNotEquals(new ElementFilter(ns1c), + new ElementFilter(ns1c).negate()); + +} + +} diff --git a/test/src/java/org/jdom/test/cases/filter/TestFilters.java b/test/src/java/org/jdom/test/cases/filter/TestFilters.java new file mode 100644 index 0000000..d4aa15a --- /dev/null +++ b/test/src/java/org/jdom/test/cases/filter/TestFilters.java @@ -0,0 +1,211 @@ +package org.jdom.test.cases.filter; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter2.ContentFilter; +import org.jdom.filter2.Filter; +import org.jdom.filter2.Filters; +import org.jdom.test.util.UnitTestUtil; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestFilters extends AbstractTestFilter { + + + private void checkFilter(Filter filter, F match, Object not) { + assertNotNull(filter); + assertNotNull(filter.toString()); + assertFilterEquals(filter, UnitTestUtil.deSerialize(filter)); + assertFilterNotEquals(filter, filter.negate()); + assertFalse(filter.equals(null)); + assertTrue(filter.matches(match)); + assertFalse(filter.matches(not)); + ArrayList al = null; + assertTrue(filter.filter(al).isEmpty()); + al = new ArrayList(3); + al.add(not); + al.add(match); + al.add(null); + List mat = filter.filter(al); + + assertTrue(mat.size() == 1); + assertTrue(mat.get(0) == match); + } + + @Test + public void testContent() { + checkFilter(Filters.content(), new Element("tag"), new Object()); + checkFilter(Filters.content(), new Element("tag"), null); + } + + @Test + public void testDocument() { + checkFilter(Filters.document(), new Document(), new Object()); + } + + @Test + public void testAttribute() { + checkFilter(Filters.attribute(), new Attribute("tag", "val"), new Object()); + } + + @Test + public void testAttributeString() { + checkFilter(Filters.attribute("mat"), new Attribute("mat", "val"), new Attribute("not", "val")); + } + + @Test + public void testAttributeStringNamespace() { + Namespace nsa = Namespace.getNamespace("pfa", "uria"); + Namespace nsb = Namespace.getNamespace("pfb", "urib"); + checkFilter(Filters.attribute("mat", nsa), + new Attribute("mat", "val", nsa), new Object()); + checkFilter(Filters.attribute("mat", nsa), + new Attribute("mat", "val", nsa), new Attribute("mat", "val", nsb)); + checkFilter(Filters.attribute("mat", nsa), + new Attribute("mat", "val", nsa), new Attribute("not", "val", nsa)); + checkFilter(Filters.attribute("mat", nsa), + new Attribute("mat", "val", nsa), new Attribute("mat", "val")); + checkFilter(Filters.attribute("mat", null), + new Attribute("mat", "val", nsa), new Attribute("not", "val", nsa)); + checkFilter(Filters.attribute(null, nsa), + new Attribute("mat", "val", nsa), new Attribute("mat", "val", nsb)); + checkFilter(Filters.attribute(null, null), + new Attribute("mat", "val", nsa), new Element("mat", nsb)); + } + + @Test + public void testAttributeNamespace() { + Namespace nsa = Namespace.getNamespace("pfa", "uria"); + Namespace nsb = Namespace.getNamespace("pfb", "urib"); + checkFilter(Filters.attribute(nsa), + new Attribute("mat", "val", nsa), new Object()); + checkFilter(Filters.attribute(nsa), + new Attribute("mat", "val", nsa), new Attribute("mat", "val", nsb)); + } + + @Test + public void testComment() { + checkFilter(Filters.comment(), new Comment("comment"), new Object()); + } + + @Test + public void testCdata() { + checkFilter(Filters.cdata(), new CDATA("comment"), new Object()); + } + + @Test + public void testDoctype() { + checkFilter(Filters.doctype(), new DocType("root"), new Object()); + } + + @Test + public void testEntityref() { + checkFilter(Filters.entityref(), new EntityRef("er"), new Object()); + } + + @Test + public void testElement() { + checkFilter(Filters.element(), new Element("emt"), new Object()); + } + + @Test + public void testElementString() { + Namespace nsa = Namespace.getNamespace("pfa", "uria"); + checkFilter(Filters.element("mat"), + new Element("mat"), new Object()); + checkFilter(Filters.element("mat"), + new Element("mat"), new Element("mat", nsa)); + checkFilter(Filters.element("mat"), + new Element("mat"), new Element("not", nsa)); + } + + @Test + public void testElementStringNamespace() { + Namespace nsa = Namespace.getNamespace("pfa", "uria"); + Namespace nsb = Namespace.getNamespace("pfb", "urib"); + checkFilter(Filters.element("mat", nsa), + new Element("mat", nsa), new Object()); + checkFilter(Filters.element("mat", nsa), + new Element("mat", nsa), new Element("mat", nsb)); + checkFilter(Filters.element("mat", nsa), + new Element("mat", nsa), new Element("not", nsa)); + checkFilter(Filters.element("mat", nsa), + new Element("mat", nsa), new Element("mat")); + } + + @Test + public void testElementNamespace() { + Namespace nsa = Namespace.getNamespace("pfa", "uria"); + Namespace nsb = Namespace.getNamespace("pfb", "urib"); + checkFilter(Filters.element(nsa), + new Element("mat", nsa), new Object()); + checkFilter(Filters.element(nsa), + new Element("mat", nsa), new Element("mat", nsb)); + checkFilter(Filters.element(nsa), + new Element("mat", nsa), new Element("mat")); + } + + @Test + public void testProcessinginstruction() { + checkFilter(Filters.processinginstruction(), + new ProcessingInstruction("jdomtest", ""), new Object()); + } + + @Test + public void testText() { + checkFilter(Filters.text(), new Text("txt"), new Object()); + checkFilter(Filters.text(), new CDATA("txt"), new Object()); + } + + @Test + public void testTextOnly() { + checkFilter(Filters.textOnly(), new Text("txt"), new CDATA("txt")); + } + + @Test + public void testFBoolean() { + checkFilter(Filters.fboolean(), Boolean.TRUE, new Object()); + } + + @Test + public void testFString() { + checkFilter(Filters.fstring(), new String("txt"), new Object()); + } + + @Test + public void testFDouble() { + checkFilter(Filters.fdouble(), Double.valueOf(123.45), new Object()); + } + + @Test + public void testFClass() { + checkFilter(Filters.fclass(Integer.class), Integer.valueOf(123), new Object()); + } + + @Test + public void testRefine() { + try { + new ContentFilter().refine(null); + fail("Should not be able to set null refine filter"); + } catch (NullPointerException npe) { + //good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected NullPointerException but got " + e.getClass()); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/filter/TestPassThroughFilter.java b/test/src/java/org/jdom/test/cases/filter/TestPassThroughFilter.java new file mode 100644 index 0000000..764f984 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/filter/TestPassThroughFilter.java @@ -0,0 +1,104 @@ +package org.jdom.test.cases.filter; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.RandomAccess; + +import org.jdom.filter2.Filter; +import org.jdom.filter2.Filters; +import org.jdom.test.util.UnitTestUtil; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestPassThroughFilter { + + @Test + public void testPassThroughFilterRandom() { + Filter ef = Filters.fpassthrough(); + + List lst = new ArrayList(3); + lst.add("hello"); + lst.add("there"); + lst.add("world"); + List filtered = ef.filter(lst); + assertTrue(filtered instanceof RandomAccess); + assertTrue(filtered.size() == 3); + assertEquals("hello", filtered.get(0)); + assertEquals("there", filtered.get(1)); + assertEquals("world", filtered.get(2)); + + try { + filtered.add("boo"); + UnitTestUtil.failNoException(UnsupportedOperationException.class); + } catch (Exception e) { + UnitTestUtil.checkException(UnsupportedOperationException.class, e); + } + + } + + + @Test + public void testPassThroughFilterLinked() { + Filter ef = Filters.fpassthrough(); + + List lst = new LinkedList(); + lst.add("hello"); + lst.add("there"); + lst.add("world"); + List filtered = ef.filter(lst); + assertTrue(filtered instanceof RandomAccess); + assertTrue(filtered.size() == 3); + assertEquals("hello", filtered.get(0)); + assertEquals("there", filtered.get(1)); + assertEquals("world", filtered.get(2)); + + try { + filtered.add("boo"); + UnitTestUtil.failNoException(UnsupportedOperationException.class); + } catch (Exception e) { + UnitTestUtil.checkException(UnsupportedOperationException.class, e); + } + + } + + + @Test + public void testPassThroughFilterNull() { + Filter ef = Filters.fpassthrough(); + + List lst = null; + List filtered = ef.filter(lst); + assertTrue(filtered instanceof RandomAccess); + assertTrue(filtered.size() == 0); + + try { + filtered.add("boo"); + UnitTestUtil.failNoException(UnsupportedOperationException.class); + } catch (Exception e) { + UnitTestUtil.checkException(UnsupportedOperationException.class, e); + } + + } + + @Test + public void testPassThroughFilterEmpty() { + Filter ef = Filters.fpassthrough(); + + List lst = new LinkedList(); + List filtered = ef.filter(lst); + assertTrue(filtered instanceof RandomAccess); + assertTrue(filtered.size() == 0); + + try { + filtered.add("boo"); + UnitTestUtil.failNoException(UnsupportedOperationException.class); + } catch (Exception e) { + UnitTestUtil.checkException(UnsupportedOperationException.class, e); + } + + } +} diff --git a/test/src/java/org/jdom/test/cases/input/HelpTestDOMBuilder.java b/test/src/java/org/jdom/test/cases/input/HelpTestDOMBuilder.java new file mode 100644 index 0000000..221c18f --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/HelpTestDOMBuilder.java @@ -0,0 +1,51 @@ +package org.jdom.test.cases.input; + +import java.io.IOException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import org.jdom.test.util.FidoFetch; + +/** + * This class encapsulates all the org.w3c.dom.DOM details, so that the actual + * TestDOMBuilder class has a cleaner import * setup with just + * JDOM imports. + * @author rolf + * + */ +@SuppressWarnings("javadoc") +public class HelpTestDOMBuilder { + + public static final Document getDocument(String resname, boolean xsdvalidate) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(true); + dbf.setValidating(xsdvalidate); + dbf.setExpandEntityReferences(false); + + if (xsdvalidate) { + dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "http://www.w3.org/2001/XMLSchema"); + } + DocumentBuilder db = dbf.newDocumentBuilder(); + InputSource is = new InputSource(FidoFetch.getFido().getURL(resname).toExternalForm()); + return db.parse(is); + } + + public static final Element getRoot(Document doc) { + Node n = doc.getFirstChild(); + while (n != null) { + if (n instanceof Element) { + return (Element)n; + } + n = n.getNextSibling(); + } + return null; + } +} diff --git a/test/src/java/org/jdom/test/cases/input/TestBuilderErrorHandler.java b/test/src/java/org/jdom/test/cases/input/TestBuilderErrorHandler.java new file mode 100644 index 0000000..784437e --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestBuilderErrorHandler.java @@ -0,0 +1,50 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.*; + +import org.jdom.input.sax.BuilderErrorHandler; + +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +@SuppressWarnings("javadoc") +public class TestBuilderErrorHandler { + + @Test + public void testWarning() { + BuilderErrorHandler handler = new BuilderErrorHandler(); + try { + handler.warning(new SAXParseException(null, null, null, 0, 0)); + } catch (Exception e) { + fail("Warning should not throw an exception, but got " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testError() { + BuilderErrorHandler handler = new BuilderErrorHandler(); + try { + handler.error(new SAXParseException(null, null, null, 0, 0)); + fail("Error should throw a SAXException, but did not"); + } catch (SAXException spe) { + //good + } catch (Exception e) { + fail("Error should throw a SAXException, but got " + e.getClass() + ": " + e.getMessage()); + } + } + + @Test + public void testFatalError() { + BuilderErrorHandler handler = new BuilderErrorHandler(); + try { + handler.fatalError(new SAXParseException(null, null, null, 0, 0)); + fail("Error should throw a SAXException, but did not"); + } catch (SAXException spe) { + //good + } catch (Exception e) { + fail("Error should throw a SAXException, but got " + e.getClass() + ": " + e.getMessage()); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestDOMBuilder.java b/test/src/java/org/jdom/test/cases/input/TestDOMBuilder.java new file mode 100644 index 0000000..28b188a --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestDOMBuilder.java @@ -0,0 +1,173 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.CharArrayWriter; +import java.io.IOException; + +import javax.xml.parsers.DocumentBuilderFactory; + +import org.junit.Test; +import org.jdom.Attribute; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.input.DOMBuilder; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaders; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestDOMBuilder { + + @Test + public void testDOMBuilder() { + DOMBuilder db = new DOMBuilder(); + assertNotNull(db); + } + + @Test + public void testFactory() { + DOMBuilder db = new DOMBuilder(); + assertTrue(db.getFactory() instanceof DefaultJDOMFactory); + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertFalse(db.getFactory() == fac); + db.setFactory(fac); + assertTrue(db.getFactory() == fac); + } + + @Test + public void testSimpleDocument() { + checkDOM("/DOMBuilder/simple.xml", false); + } + + @Test + public void testAttributesDocument() { + checkDOM("/DOMBuilder/attributes.xml", false); + } + + @Test + public void testNamespaceDocument() { + checkDOM("/DOMBuilder/namespaces.xml", false); + } + + @Test + public void testDocTypeDocument() { + checkDOM("/DOMBuilder/doctype.xml", false); + } + + @Test + public void testComplexDocument() { + checkDOM("/DOMBuilder/complex.xml", false); + } + + @Test + public void testXSDDocument() { + checkDOM("/xsdcomplex/input.xml", true); + } + + @Test + public void testNoNamespaceDOM() throws Exception { + // https://github.com/hunterhacker/jdom/issues/138 + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + org.w3c.dom.Document doc = dbFactory.newDocumentBuilder().newDocument(); + doc.setXmlVersion("1.0"); + + org.w3c.dom.Element root = doc.createElement("Document"); + + root.setAttribute("xmlns", "urn:iso:foo"); + root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + root.setAttribute("xsi:schemaLocation", "urn:iso:foo bar.xsd"); + doc.appendChild(root); + + // The above is a badly-formed DOM document without the correct + // namespaceing. The second attribute should use root.setAttributeNS + DOMBuilder dbuilder = new DOMBuilder(); + Document jdoc = dbuilder.build(doc); + + Namespace xsi = Namespace.getNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance"); + Attribute att = jdoc.getRootElement().getAttribute("schemaLocation", xsi); + assertTrue(att != null); + assertTrue("xsi".equals(att.getNamespacePrefix())); + + } + + private void checkDOM(String resname, boolean xsdvalidate) { + try { + org.w3c.dom.Document domdoc = HelpTestDOMBuilder.getDocument(resname, xsdvalidate); + DOMBuilder db = new DOMBuilder(); + Document dombuild = db.build(domdoc); + Element domroot = db.build(HelpTestDOMBuilder.getRoot(domdoc)); + + SAXBuilder sb = new SAXBuilder(xsdvalidate + ? XMLReaders.XSDVALIDATING + : XMLReaders.NONVALIDATING ); + sb.setExpandEntities(false); + + Document saxbuild = sb.build(FidoFetch.getFido().getURL(resname)); + Element saxroot = saxbuild.hasRootElement() ? saxbuild.getRootElement() : null; + + assertEquals(toString(saxbuild), toString(dombuild)); + assertEquals(toString(saxroot), toString(domroot)); + + } catch (Exception e) { + UnitTestUtil.failException( + "Could not parse file '" + resname + "': " + e.getMessage(), e); + } + } + + private void normalizeDTD(DocType dt) { + if (dt == null) { + return; + } + // do some tricks so that we can compare the results. + // these may well break the actual syntax of DTD's but for testing + // purposes it is OK. + String internalss = dt.getInternalSubset().trim() ; + // the spaceing in and around the internal subset is different between + // our SAX parse, and the DOM parse. + // make all whitespace a single space. + internalss = internalss.replaceAll("\\s+", " "); + // It seems the DOM parser internally quotes entities with single quote + // but our sax parser uses double-quote. + // simply replace all " with ' and be done with it. + internalss = internalss.replaceAll("\"", "'"); + dt.setInternalSubset("\n" + internalss + "\n"); + } + + private String toString(Document doc) { + UnitTestUtil.normalizeAttributes(doc.getRootElement()); + normalizeDTD(doc.getDocType()); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + CharArrayWriter caw = new CharArrayWriter(); + try { + out.output(doc, caw); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return caw.toString(); + } + + private String toString(Element emt) { + UnitTestUtil.normalizeAttributes(emt); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + CharArrayWriter caw = new CharArrayWriter(); + try { + out.output(emt, caw); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return caw.toString(); + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestDTDParser.java b/test/src/java/org/jdom/test/cases/input/TestDTDParser.java new file mode 100644 index 0000000..7af37e9 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestDTDParser.java @@ -0,0 +1,336 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.*; + +import org.jdom.DocType; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.DefaultJDOMFactory; +import org.jdom.input.stax.DTDParser; +import org.jdom.test.util.UnitTestUtil; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestDTDParser { + + private static final JDOMFactory factory = new DefaultJDOMFactory(); + + @Test + public void testParseSimple() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseSimpleCompact() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseSimpleCompactInternal() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals("internal", dt.getInternalSubset()); + } + + @Test + public void testParseSYSTEMquotNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseSYSTEMaposNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseSYSTEMquotSimple() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals("internal", dt.getInternalSubset()); + } + + @Test + public void testParseSYSTEMaposSimple() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals("internal", dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICquotenullNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICaposnullNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICquotquotNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICquotaposNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICaposquotNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICaposaposNONE() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICaposaposSimple() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals("internal", dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICaposaposSimpleCompact() throws JDOMException { + DocType dt = DTDParser.parse( + "", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals("public", dt.getPublicID()); + assertEquals("system", dt.getSystemID()); + assertEquals("internal", dt.getInternalSubset()); + } + + @Test + public void testParsePUBLICaposaposSimpleSpacy() throws JDOMException { + DocType dt = DTDParser.parse( + " ] > ", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(" public ", dt.getPublicID()); + assertEquals(" system ", dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalA() throws JDOMException { + DocType dt = DTDParser.parse( + "]>", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalEmbeddedNewlines() throws JDOMException { + DocType dt = DTDParser.parse( + " \n \n ] \n >", + factory); + + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n \n", dt.getInternalSubset()); + } + + @Test + public void testParseIncomplete() { + try { + DTDParser.parse("",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseTab() throws JDOMException { + DocType dt = DTDParser.parse("",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseNewline() throws JDOMException { + DocType dt = DTDParser.parse("",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseCarriageReturn() throws JDOMException { + DocType dt = DTDParser.parse("",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(null, dt.getInternalSubset()); + } + + @Test + public void testParseInternalSpace() throws JDOMException { + DocType dt = DTDParser.parse(" ] >",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalTab() throws JDOMException { + DocType dt = DTDParser.parse("\t]\t>",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalNewline() throws JDOMException { + DocType dt = DTDParser.parse("\n]\n>",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalCarriageReturn() throws JDOMException { + DocType dt = DTDParser.parse("\r]\r>",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalWithAPosSpace() throws JDOMException { + DocType dt = DTDParser.parse("]>",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + + @Test + public void testParseInternalWithQuoteSpace() throws JDOMException { + DocType dt = DTDParser.parse("]>",factory); + assertEquals("root", dt.getElementName()); + assertEquals(null, dt.getPublicID()); + assertEquals(null, dt.getSystemID()); + assertEquals(" \n", dt.getInternalSubset()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestJDOMParseExceptn.java b/test/src/java/org/jdom/test/cases/input/TestJDOMParseExceptn.java new file mode 100644 index 0000000..052f01c --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestJDOMParseExceptn.java @@ -0,0 +1,60 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.*; + +import org.jdom.Document; +import org.jdom.input.JDOMParseException; +import org.junit.Test; +import org.xml.sax.SAXParseException; + +// Do not use name ending in Exception. +@SuppressWarnings("javadoc") +public class TestJDOMParseExceptn { + + private final SAXParseException spe = new SAXParseException("message", "publicID", "systemID", 5, 10); + + @Test + public void testJDOMParseExceptionStringThrowable() { + JDOMParseException e = new JDOMParseException("test", spe); + assertTrue(e.getPartialDocument() == null); + } + + @Test + public void testJDOMParseExceptionStringThrowableDocument() { + Document doc = new Document(); + JDOMParseException e = new JDOMParseException("test", spe, doc); + assertTrue(e.getPartialDocument() == doc); + } + + @Test + public void testGetPartialDocument() { + Document doc = new Document(); + JDOMParseException e = new JDOMParseException("test", spe, doc); + assertTrue(e.getPartialDocument() == doc); + } + + @Test + public void testGetPublicId() { + assertEquals("publicID", new JDOMParseException("test", spe).getPublicId()); + assertTrue(null== new JDOMParseException("test", new Exception()).getPublicId()); + } + + @Test + public void testGetSystemId() { + assertEquals("systemID", new JDOMParseException("test", spe).getSystemId()); + assertTrue(null== new JDOMParseException("test", new Exception()).getSystemId()); + } + + @Test + public void testGetLineNumber() { + assertEquals(5, new JDOMParseException("test", spe).getLineNumber()); + assertTrue(-1 == new JDOMParseException("test", new Exception()).getLineNumber()); + } + + @Test + public void testGetColumnNumber() { + assertEquals(10, new JDOMParseException("test", spe).getColumnNumber()); + assertTrue(-1 == new JDOMParseException("test", new Exception()).getColumnNumber()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestSAXBuilder.java b/test/src/java/org/jdom/test/cases/input/TestSAXBuilder.java new file mode 100644 index 0000000..8cc1084 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestSAXBuilder.java @@ -0,0 +1,1334 @@ +/*-- + +Copyright (C) 2000 Brett McLaughlin & Jason Hunter. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact license@jdom.org. + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management (pm@jdom.org). + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Brett McLaughlin and + Jason Hunter . For more information on the + JDOM Project, please see . + + */ + + +package org.jdom.test.cases.input; + +/** + * Tests of SAXBuilder functionality. Since most of these methods are tested in other parts + * of the test suite, many tests are not filled. + * + * @author Philip Nelson + * @version 0.5 + */ +import static org.jdom.test.util.UnitTestUtil.checkException; +import static org.jdom.test.util.UnitTestUtil.failNoException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.CharBuffer; +import java.util.Arrays; +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLFilter; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.XMLFilterImpl; + +import org.jdom.Content; +import org.jdom.DefaultJDOMFactory; +import org.jdom.Document; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.UncheckedJDOMFactory; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.BuilderErrorHandler; +import org.jdom.input.sax.SAXEngine; +import org.jdom.input.sax.SAXHandler; +import org.jdom.input.sax.SAXHandlerFactory; +import org.jdom.input.sax.XMLReaderJDOMFactory; +import org.jdom.input.sax.XMLReaderSAX2Factory; +import org.jdom.input.sax.XMLReaders; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + + +@SuppressWarnings("javadoc") +public final class TestSAXBuilder { + + private static final String testxml = ""; + private static final String testpattern = "\\s*<\\?xml\\s+version=\"1.0\"\\s+encoding=\"UTF-8\"\\s*\\?>\\s*\\s*"; + + private class MySAXBuilder extends SAXBuilder { + public MySAXBuilder() { + super(); + } + + @SuppressWarnings("deprecation") + public MySAXBuilder(String driver) { + super(driver); + } + + public MySAXBuilder(XMLReaderJDOMFactory fac) { + super(fac); + } + + /** + * This sets and configures the parser (SAXBuilder just sets). + */ + @Override + public XMLReader createParser() throws JDOMException { + XMLReader reader = super.createParser(); + configureParser(reader, new SAXHandler()); + return reader; + } + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilder() { + SAXBuilder sb = new SAXBuilder(); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilderBooleanFalse() { + SAXBuilder sb = new SAXBuilder(false); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilderBooleanTrue() { + SAXBuilder sb = new SAXBuilder(true); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilderString() { + MySAXBuilder sb = new MySAXBuilder("org.apache.xerces.parsers.SAXParser"); + assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + try { + XMLReader reader = sb.createParser(); + assertNotNull(reader); + assertEquals("org.apache.xerces.parsers.SAXParser", reader.getClass().getName()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + + sb = new MySAXBuilder("com.sun.org.apache.xerces.internal.parsers.SAXParser"); + assertEquals("com.sun.org.apache.xerces.internal.parsers.SAXParser", sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + try { + XMLReader reader = sb.createParser(); + assertNotNull(reader); + assertTrue(reader.getClass().getName().equals("com.sun.org.apache.xerces.internal.parsers.SAXParser")); + + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilderStringNew() { + XMLReaderSAX2Factory fac = new XMLReaderSAX2Factory(false, "org.apache.xerces.parsers.SAXParser"); + MySAXBuilder sb = new MySAXBuilder(fac); + assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + try { + XMLReader reader = sb.createParser(); + assertNotNull(reader); + assertEquals("org.apache.xerces.parsers.SAXParser", reader.getClass().getName()); + + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + + fac = new XMLReaderSAX2Factory(false, "com.sun.org.apache.xerces.internal.parsers.SAXParser"); + sb = new MySAXBuilder("com.sun.org.apache.xerces.internal.parsers.SAXParser"); + assertEquals("com.sun.org.apache.xerces.internal.parsers.SAXParser", sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + try { + XMLReader reader = sb.createParser(); + assertNotNull(reader); + assertTrue(reader.getClass().getName().equals("com.sun.org.apache.xerces.internal.parsers.SAXParser")); + + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilderStringTrue() { + SAXBuilder sb = new SAXBuilder("org.apache.xerces.parsers.SAXParser", true); + assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.getValidation()); + assertTrue(sb.getExpandEntities()); + } + + @SuppressWarnings("deprecation") + @Test + public void testSAXBuilderStringFalse() { + SAXBuilder sb = new SAXBuilder("org.apache.xerces.parsers.SAXParser", false); + assertEquals("org.apache.xerces.parsers.SAXParser", sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetJDOMFactory() { + SAXBuilder sb = new SAXBuilder(true); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + assertTrue(sb.getJDOMFactory() instanceof DefaultJDOMFactory); + assertTrue(sb.getJDOMFactory() == sb.getFactory()); + } + + @SuppressWarnings("deprecation") + @Test + public void testSetJDOMFactory() throws JDOMException { + SAXBuilder sb = new SAXBuilder(true); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.getValidation()); + assertTrue(sb.getExpandEntities()); + JDOMFactory fac = sb.getJDOMFactory(); + assertTrue(fac instanceof DefaultJDOMFactory); + UncheckedJDOMFactory udf = new UncheckedJDOMFactory(); + sb.setJDOMFactory(udf); + assertTrue(sb.getJDOMFactory() == udf); + assertTrue(sb.buildEngine().getJDOMFactory() == udf); + sb.setFactory(fac); + assertTrue(sb.getJDOMFactory() == fac); + assertTrue(sb.buildEngine().getJDOMFactory() == fac); + } + + @Test + public void testGetSAXHandlerFactory() { + SAXBuilder sb = new SAXBuilder(); + assertTrue(sb.getSAXHandlerFactory() != null); + } + + @SuppressWarnings("deprecation") + @Test + public void testSetSAXHandlerFactory() { + SAXBuilder sb = new SAXBuilder(true); + SAXHandlerFactory fac = sb.getSAXHandlerFactory(); + sb.setSAXHandlerFactory(null); + assertTrue(fac == sb.getSAXHandlerFactory()); + SAXHandlerFactory fbee = new SAXHandlerFactory() { + @Override + public SAXHandler createSAXHandler(JDOMFactory factory) { + return new SAXHandler(); + } + }; + sb.setSAXHandlerFactory(fbee); + assertTrue(fbee == sb.getSAXHandlerFactory()); + sb.setSAXHandlerFactory(null); + assertTrue(fac == sb.getSAXHandlerFactory()); + } + + @SuppressWarnings("deprecation") + @Test + public void testSetValidation() { + SAXBuilder sb = new SAXBuilder(true); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.getValidation()); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + sb.setValidation(false); + assertFalse(sb.getValidation()); + assertFalse(sb.isValidating()); + + sb.setValidation(true); + assertTrue(sb.getValidation()); + assertTrue(sb.isValidating()); + + } + + @SuppressWarnings("deprecation") + @Test + public void testGetErrorHandler() throws JDOMException { + SAXBuilder sb = new SAXBuilder(true); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + assertTrue(sb.buildEngine().getErrorHandler() instanceof BuilderErrorHandler); + + ErrorHandler handler = new BuilderErrorHandler(); + + sb.setErrorHandler(handler); + assertTrue(handler == sb.getErrorHandler()); + assertTrue(handler == sb.buildEngine().getErrorHandler()); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetEntityResolver() { + SAXBuilder sb = new SAXBuilder(true); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + EntityResolver er = new EntityResolver() { + @Override + public InputSource resolveEntity(String arg0, String arg1) { + return null; + } + }; + + sb.setEntityResolver(er); + assertTrue(er == sb.getEntityResolver()); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetDTDHandler() { + SAXBuilder sb = new SAXBuilder(true); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + DTDHandler dtd = new DTDHandler() { + @Override + public void notationDecl(String arg0, String arg1, String arg2) + throws SAXException { + // do nothing + } + @Override + public void unparsedEntityDecl(String arg0, String arg1, + String arg2, String arg3) throws SAXException { + // do nothing + } + }; + + sb.setDTDHandler(dtd); + assertTrue(dtd == sb.getDTDHandler()); + } + + @Test + public void testGetSetXMLReaderFactory() { + SAXBuilder sb = new SAXBuilder(); + XMLReaderJDOMFactory xrjf = sb.getXMLReaderFactory(); + assertTrue(xrjf == XMLReaders.NONVALIDATING); + sb.setXMLReaderFactory(XMLReaders.XSDVALIDATING); + assertTrue(sb.getXMLReaderFactory() == XMLReaders.XSDVALIDATING); + sb.setXMLReaderFactory(null); + assertTrue(xrjf == XMLReaders.NONVALIDATING); + } + + @Test + public void testXMLFilter() { + MySAXBuilder sb = new MySAXBuilder(); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.getExpandEntities()); + + XMLFilter filter = new XMLFilterImpl() { + @Override + public void startElement(String arg0, String arg1, String arg2, + Attributes arg3) throws SAXException { + super.startElement(arg0, "f" + arg1, arg2, arg3); + } + @Override + public void endElement(String arg0, String arg1, String arg2) throws SAXException { + super.endElement(arg0, "f" + arg1, arg2); + } + }; + + XMLFilter gilter = new XMLFilterImpl() { + @Override + public void startElement(String arg0, String arg1, String arg2, + Attributes arg3) throws SAXException { + super.startElement(arg0, "g" + arg1, arg2, arg3); + } + @Override + public void endElement(String arg0, String arg1, String arg2) throws SAXException { + super.endElement(arg0, "g" + arg1, arg2); + } + }; + + filter.setParent(gilter); + sb.setXMLFilter(filter); + assertTrue(filter == sb.getXMLFilter()); + + try { + Document doc = sb.build(new CharArrayReader(testxml.toCharArray())); + assertTrue(doc.hasRootElement()); + assertEquals("fgroot", doc.getRootElement().getName()); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not parse XML " + testxml + ": " + e.getMessage()); + } + + } + + @Test + public void testGetIgnoringElementContentWhitespace() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + SAXEngine se = sb.buildEngine(); + assertFalse(se.getIgnoringBoundaryWhitespace()); + + sb.setIgnoringElementContentWhitespace(true); + assertTrue(sb.getIgnoringElementContentWhitespace()); + se = sb.buildEngine(); + assertTrue(se.getIgnoringElementContentWhitespace()); + se.build(new StringReader(testxml)); + assertTrue(se.getIgnoringElementContentWhitespace()); + sb.setIgnoringElementContentWhitespace(false); + assertFalse(sb.getIgnoringElementContentWhitespace()); + se = sb.buildEngine(); + assertFalse(se.getIgnoringElementContentWhitespace()); + } + + @Test + public void testGetIgnoringBoundaryWhitespace() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + SAXEngine se = sb.buildEngine(); + assertNull(sb.getDriverClass()); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertFalse(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + assertTrue(se.getEntityResolver() == null); + assertTrue(se.getErrorHandler() != null); + assertTrue(se.getDTDHandler() != null); + assertFalse(se.isValidating()); + assertTrue(se.getExpandEntities()); + sb.setIgnoringBoundaryWhitespace(true); + assertTrue(sb.getIgnoringBoundaryWhitespace()); + se = sb.buildEngine(); + assertTrue(se.getIgnoringBoundaryWhitespace()); + se.build(new StringReader(testxml)); + assertTrue(se.getIgnoringBoundaryWhitespace()); + sb.setIgnoringBoundaryWhitespace(false); + assertFalse(sb.getIgnoringBoundaryWhitespace()); + + se = sb.buildEngine(); + assertFalse(se.getIgnoringBoundaryWhitespace()); + } + + @Test + public void testGetExpandEntities() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + assertTrue(sb.getExpandEntities()); + sb.setExpandEntities(false); + SAXEngine se = sb.buildEngine(); + assertFalse(se.getExpandEntities()); + se = sb.buildEngine(); + assertFalse(se.getExpandEntities()); + se.build(new StringReader(testxml)); + assertFalse(se.getExpandEntities()); + + sb.setExpandEntities(true); + assertTrue(sb.getExpandEntities()); + se = sb.buildEngine(); + assertTrue(se.getExpandEntities()); + se.build(new StringReader(testxml)); + assertTrue(se.getExpandEntities()); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetReuseParser() { + SAXBuilder sb = new SAXBuilder(true); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + sb.setReuseParser(true); + assertTrue(sb.getReuseParser()); + sb.setReuseParser(false); + assertFalse(sb.getReuseParser()); + } + + @Test + public void testReuseParser() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + assertTrue(sb.getReuseParser()); + + sb.build(new StringReader("\n\n")); + Document document = sb.build(new StringReader(testxml)); + assertXMLMatches(null, document); + } + + @Test + public void testCreateParser() { + MySAXBuilder sb = new MySAXBuilder(); + try { + XMLReader reader = sb.createParser(); + assertNotNull(reader); + } catch (JDOMException e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + } + + @Test + public void testSetFeature() { + String feature = "http://javax.xml.XMLConstants/feature/secure-processing"; + MySAXBuilder sb = new MySAXBuilder(); + try { + sb.setFeature(feature, true); + XMLReader reader = sb.createParser(); + assertNotNull(reader); + assertTrue(reader.getFeature(feature)); + sb.setFeature(feature, false); + reader = sb.createParser(); + assertNotNull(reader); + assertFalse(reader.getFeature(feature)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + } + + @Test + public void testSetProperty() { + LexicalHandler lh = new LexicalHandler() { + @Override + public void startEntity(String arg0) throws SAXException { + // Do nothing; + } + @Override + public void startDTD(String arg0, String arg1, String arg2) + throws SAXException { + // Do nothing; + } + @Override + public void startCDATA() throws SAXException { + // Do nothing; + } + @Override + public void endEntity(String arg0) throws SAXException { + // Do nothing; + } + + @Override + public void endDTD() throws SAXException { + // Do nothing; + } + + @Override + public void endCDATA() throws SAXException { + // Do nothing; + } + + @Override + public void comment(char[] arg0, int arg1, int arg2) throws SAXException { + // Do nothing; + } + }; + + MySAXBuilder sb = new MySAXBuilder(); + String propname = "http://xml.org/sax/properties/lexical-handler"; + try { + sb.setProperty(propname, lh); + XMLReader reader = sb.createParser(); + assertNotNull(reader); + assertTrue(lh == reader.getProperty(propname)); + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + sb.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "XMLSchema"); + + } + + @Test + public void testSetPropertyTwo() { + MySAXBuilder sb = new MySAXBuilder(); + try { + sb.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage", "XMLSchema"); + sb.createParser(); + fail("Should not be able to set the property"); + } catch (JDOMException jde) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Could not create parser: " + e.getMessage()); + } + + } + + /** + * Test that when setExpandEntities is true, enties are + * always expanded and when false, entities declarations + * are added to the DocType + */ + @Test + public void test_TCM__void_setExpandEntities_boolean() throws JDOMException, IOException { + //test entity exansion on internal entity + + URL src = FidoFetch.getFido().getURL("/SAXBuilderTestEntity.xml"); + + SAXBuilder builder = new SAXBuilder(); + + builder.setExpandEntities(true); + assertTrue(builder.getExpandEntities()); + + Document doc = builder.build(src); + assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("simple entity") == 0); + assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("another simple entity") > 1); + + //test that entity declaration appears in doctype + //and EntityRef is created in content with internal entity + builder.setExpandEntities(false); + assertFalse(builder.getExpandEntities()); + + doc = builder.build(src); + assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("simple entity") > 1)); + assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("another simple entity") > 1)); + List content = doc.getRootElement().getContent(); + assertTrue("didn't get EntityRef for unexpanded entities", + content.get(0) instanceof EntityRef); + assertTrue("didn't get EntityRef for unexpanded entities", + content.get(2) instanceof EntityRef); + + //test entity expansion on external entity + URL src2 = FidoFetch.getFido().getURL("/SAXBuilderTestEntity2.xml"); + + builder.setExpandEntities(true); + assertTrue(builder.getExpandEntities()); + + doc = builder.build(src2); + assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("simple entity") == 0); + assertTrue("didn't get entity text", doc.getRootElement().getText().indexOf("another simple entity") > 1); + + //test that entity declaration appears in doctype + //and EntityRef is created in content with external entity + builder.setExpandEntities(false); + assertFalse(builder.getExpandEntities()); + doc = builder.build(src2); + assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("simple entity") > 1)); + assertTrue("got entity text", ! (doc.getRootElement().getText().indexOf("another simple entity") > 1)); + content = doc.getRootElement().getContent(); + assertTrue("didn't get EntityRef for unexpanded entities", + content.get(0) instanceof EntityRef); + assertTrue("didn't get EntityRef for unexpanded entities", + content.get(2) instanceof EntityRef); + + + + } + + /** + * Test that when setExpandEntities is true, enties are + * always expanded and when false, entities declarations + * are added to the DocType + */ + @Test + public void test_TCU__DTDComments() throws JDOMException, IOException { + //test entity exansion on internal entity + + SAXBuilder builder = new SAXBuilder(); + //test entity expansion on external entity + URL file = FidoFetch.getFido().getURL("/SAXBuilderTestDecl.xml"); + + //test that entity declaration appears in doctype + //and EntityRef is created in content with external entity + builder.setExpandEntities(false); + Document doc = builder.build(file); + + assertTrue("didnt' get internal subset comments correctly", doc.getDocType().getInternalSubset().indexOf("foo") > 0); + //assertTrue("didn't get EntityRef for unexpanded attribute entities", + // doc.getRootElement().getAttribute("test").getValue().indexOf("&simple") == 0); + + + + } + + /** + * Test that when setExpandEntities is true, enties are + * always expanded and when false, entities declarations + * are added to the DocType + */ + @Test + public void test_TCU__InternalAndExternalEntities() throws JDOMException, IOException { + //test entity exansion on internal entity + + SAXBuilder builder = new SAXBuilder(); + //test entity expansion on internal and external entity + URL file = FidoFetch.getFido().getURL("/SAXBuilderTestIntExtEntity.xml"); + + builder.setExpandEntities(true); + Document doc = builder.build(file); + assertTrue("didn't get internal entity text", doc.getRootElement().getText().indexOf("internal") >= 0); + assertTrue("didn't get external entity text", doc.getRootElement().getText().indexOf("external") > 0); + //the internal subset should be empty since entity expansion is off + assertTrue("invalid characters in internal subset", doc.getDocType().getInternalSubset().length() == 0); + assertTrue("incorrectly got entity declaration in internal subset for internal entity", + doc.getDocType().getInternalSubset().indexOf("internal") < 0); + assertTrue("incorrectly got external entity declaration in internal subset", + doc.getDocType().getInternalSubset().indexOf("external") < 0); + assertTrue("incorrectly got external entity declaration in internal subset", + doc.getDocType().getInternalSubset().indexOf("ldquo") < 0); + //test that local entity declaration appears in internal subset + //and EntityRef is created in content with external entity + builder.setExpandEntities(false); + doc = builder.build(file); + + EntityRef internal = (EntityRef)doc.getRootElement().getContent().get(0); + EntityRef external = (EntityRef)doc.getRootElement().getContent().get(6); + assertNotNull("didn't get EntityRef for unexpanded internal entity", internal); + assertNotNull("didn't get EntityRef for unexpanded external entity", external); + assertTrue("didn't get local entity declaration in internal subset", + doc.getDocType().getInternalSubset().indexOf("internal") > 0); + assertTrue("incorrectly got external entity declaration in internal subset", + doc.getDocType().getInternalSubset().indexOf("external") < 0); + assertTrue("incorrectly got external entity declaration in internal subset", + doc.getDocType().getInternalSubset().indexOf("ldquo") < 0); + } + + @Ignore + @Test + public void test_TCU__InternalSubset() throws JDOMException, IOException { + + SAXBuilder builder = new SAXBuilder(); + //test entity expansion on internal subset + URL file = FidoFetch.getFido().getURL("/SAXBuilderTestEntity.xml"); + + builder.setExpandEntities(true); + Document doc = builder.build(file); + String subset = doc.getDocType().getInternalSubset(); + assertEquals("didn't get correct internal subset when expand entities was on" + , " \n \n \n", + subset); + //now do it with expansion off + builder.setExpandEntities(false); + doc = builder.build(file); + String subset2 = doc.getDocType().getInternalSubset(); + final String expect = "\n \n"; + if (!expect.equals(subset2)) { + fail("didn't get correct internal subset when expand entities was off.\n" + + "Expect: " + expect + "\n" + + "Got: " + subset2); + } + } + + @SuppressWarnings("deprecation") + @Test + public void testSetFastReconfigure() { + SAXBuilder sb = new SAXBuilder(true); + assertTrue(sb.getEntityResolver() == null); + assertTrue(sb.getErrorHandler() == null); + assertTrue(sb.getDTDHandler() == null); + assertTrue(sb.getXMLFilter() == null); + assertTrue(sb.isValidating()); + assertTrue(sb.getExpandEntities()); + + sb.setFastReconfigure(true); + + // TODO - Now what? + } + + private void assertXMLMatches(String baseuri, Document doc) { + XMLOutputter2 out = new XMLOutputter2(Format.getCompactFormat()); + try { + CharArrayWriter caw = new CharArrayWriter(); + out.output(doc, caw); + String output = caw.toString(); + if (!output.matches(testpattern)) { + fail ("Failed to match output:\n " + output + "\nwith pattern:\n " + testpattern); + } + } catch (IOException e) { + e.printStackTrace(); + UnitTestUtil.failException("Failed to write Document " + doc + " to CharArrayWriter.", e); + } + if (baseuri == null) { + assertNull(doc.getBaseURI()); + } else { + if (!baseuri.equals(doc.getBaseURI())) { + try { + final String moduri = baseuri.replaceFirst(":/", ":///"); + if (!moduri.equals(doc.getBaseURI())) { + final String fileuri = new File(baseuri).toURI().toURL().toExternalForm(); + if (!fileuri.equals(doc.getBaseURI())) { + final String modfileuri = fileuri.replaceFirst(":/", ":///"); + if (!modfileuri.equals(doc.getBaseURI())) { + fail("Base URI " + doc.getBaseURI() + " is not one of " + + Arrays.toString(new String[]{baseuri, moduri, fileuri, modfileuri})); + } + } + } + } catch (MalformedURLException mue) { + UnitTestUtil.failException("Could not create File URL", mue); + } + } + } + } + + @Test + public void testBuildInputSource() { + try { + SAXBuilder sb = new SAXBuilder(); + InputSource is = null; + is = new InputSource(new CharArrayReader(testxml.toCharArray())); + assertXMLMatches(null, sb.build(is)); + is = new InputSource(new CharArrayReader(testxml.toCharArray())); + assertXMLMatches(null, sb.build(is)); + sb.setReuseParser(false); + is = new InputSource(new CharArrayReader(testxml.toCharArray())); + assertXMLMatches(null, sb.build(is)); + is = new InputSource(new CharArrayReader(testxml.toCharArray())); + assertXMLMatches(null, sb.build(is)); + + is = new InputSource(new CharArrayReader(testxml.toCharArray())); + assertXMLMatches(null, sb.buildEngine().build(is)); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to parse document: " + e.getMessage()); + } + } + + @Test + public void testBuildInputStream() { + byte[] bytes = testxml.getBytes(); + try { + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes))); + assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes))); + sb.setReuseParser(false); + assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes))); + assertXMLMatches(null, sb.build(new ByteArrayInputStream(bytes))); + + assertXMLMatches(null, sb.buildEngine().build(new ByteArrayInputStream(bytes))); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to parse document: " + e.getMessage()); + } + } + + @Test + public void testBuildFile() { + File tmp = null; + try { + tmp = File.createTempFile("tst", ".xml"); + tmp.deleteOnExit(); + FileWriter fw = new FileWriter(tmp); + fw.write(testxml.toCharArray()); + fw.flush(); + fw.close(); + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp)); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp)); + sb.setReuseParser(false); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp)); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp)); + assertXMLMatches(tmp.toURI().toString(), sb.buildEngine().build(tmp)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to write/parse document to file '" + tmp + "': " + e.getMessage()); + } finally { + if (tmp != null) { + tmp.delete(); + } + } + } + + @Test + public void testBuildURL() { + File tmp = null; + try { + tmp = File.createTempFile("tst", ".xml"); + tmp.deleteOnExit(); + FileWriter fw = new FileWriter(tmp); + fw.write(testxml.toCharArray()); + fw.flush(); + fw.close(); + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL())); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL())); + sb.setReuseParser(false); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL())); + assertXMLMatches(tmp.toURI().toString(), sb.build(tmp.toURI().toURL())); + + assertXMLMatches(tmp.toURI().toString(), sb.buildEngine().build(tmp.toURI().toURL())); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to write/parse document to file '" + tmp + "': " + e.getMessage()); + } finally { + if (tmp != null) { + tmp.delete(); + } + } + } + + @Test + public void testBuildInputStreamString() { + byte[] bytes = testxml.getBytes(); + + try { + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches("baseID", + sb.build(new ByteArrayInputStream(bytes), "baseID")); + assertXMLMatches("baseID", + sb.build(new ByteArrayInputStream(bytes), "baseID")); + sb.setReuseParser(false); + assertXMLMatches("baseID", + sb.build(new ByteArrayInputStream(bytes), "baseID")); + assertXMLMatches("baseID", + sb.build(new ByteArrayInputStream(bytes), "baseID")); + assertXMLMatches("baseID", + sb.buildEngine().build(new ByteArrayInputStream(bytes), "baseID")); + } catch (Exception e) { + e.printStackTrace(); + UnitTestUtil.failException("Failed to parse document: " + e.getMessage(), e); + } + } + + @Test + public void testBuildReader() { + char[] chars = testxml.toCharArray(); + try { + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches(null, sb.build(new CharArrayReader(chars))); + assertXMLMatches(null, sb.build(new CharArrayReader(chars))); + sb.setReuseParser(false); + assertXMLMatches(null, sb.build(new CharArrayReader(chars))); + assertXMLMatches(null, sb.build(new CharArrayReader(chars))); + + assertXMLMatches(null, sb.buildEngine().build(new CharArrayReader(chars))); + } catch (Exception e) { + e.printStackTrace(); + UnitTestUtil.failException("Failed to parse document: " + e.getMessage(), e); + } + } + + @Test + public void testBuildReaderString() { + char[] chars = testxml.toCharArray(); + try { + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches("baseID", + sb.build(new CharArrayReader(chars), "baseID")); + assertXMLMatches("baseID", + sb.build(new CharArrayReader(chars), "baseID")); + sb.setReuseParser(false); + assertXMLMatches("baseID", + sb.build(new CharArrayReader(chars), "baseID")); + assertXMLMatches("baseID", + sb.build(new CharArrayReader(chars), "baseID")); + + assertXMLMatches("baseID", + sb.buildEngine().build(new CharArrayReader(chars), "baseID")); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to parse document: " + e.getMessage()); + } + } + + @Test + public void testBuildString() { + File tmp = null; + try { + tmp = File.createTempFile("tst", ".xml"); + tmp.deleteOnExit(); + FileWriter fw = new FileWriter(tmp); + fw.write(testxml.toCharArray()); + fw.flush(); + fw.close(); + SAXBuilder sb = new SAXBuilder(); + assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), + sb.build(tmp.toString())); + assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), + sb.build(tmp.toString())); + sb.setReuseParser(false); + assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), + sb.build(tmp.toString())); + assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), + sb.build(tmp.toString())); + + assertXMLMatches(tmp.getCanonicalFile().toURI().toURL().toString(), + sb.buildEngine().build(tmp.toString())); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to write/parse document to file '" + tmp + "': " + e.getMessage()); + } finally { + if (tmp != null) { + tmp.delete(); + } + } + } + + @Test + public void testBuildStringNegativeNull() { + SAXBuilder sb = new SAXBuilder(); + try { + String n = null; + sb.build(n); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + } + + @Test + public void testBuildStringNegativeBadURI() { + SAXBuilder sb = new SAXBuilder(); + try { + sb.build(" `!@#$%^&*() is not a valid URI "); + failNoException(MalformedURLException.class); + } catch (Exception e) { + checkException(MalformedURLException.class, e); + if (e.getCause() != null) { + assertFalse(e.getCause() instanceof MalformedURLException); + } + } + } + + @Test + public void testXMLNamesVariants() throws JDOMException, IOException { + String toparse = ""; + + SAXBuilder sb = new SAXBuilder(); + Document doc = sb.build(new CharArrayReader(toparse.toCharArray())); + + assertEquals("Should match: xml", "xml" , doc.getRootElement().getAttributeValue("xml")); + + + } + + @Test + public void testBuildStringNegativeActualXML() { + SAXBuilder sb = new SAXBuilder(); + try { + sb.build(""); + failNoException(IOException.class); + } catch (Exception e) { + checkException(IOException.class, e); + // cause should also be a MalformedURLException + checkException(IOException.class, e.getCause()); + } + } + + @Test + public void testBuildStringNegativePaddedXML() { + SAXBuilder sb = new SAXBuilder(); + try { + sb.build(" "); + failNoException(IOException.class); + } catch (Exception e) { + checkException(IOException.class, e); + checkException(IOException.class, e.getCause()); + } + } + + @Test + public void testSimpleCDATA() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + Document doc = sb.build(new StringReader("")); + List content = doc.getRootElement().getContent(); + assertEquals("Should be only one child: " + content, 1 , content.size()); + } + + @Test + public void testSplitCDATAinCDATA() throws JDOMException, IOException { + // Note the ]]> but was:<[null]> ]]>"; + + SAXBuilder sb = new SAXBuilder(); + Document doc = sb.build(new CharArrayReader(toparse.toCharArray())); + + assertEquals("Should match: expected:<[[D/0]]> but was:<[null]>", " expected:<[[D/0]]> but was:<[null]> ", doc.getRootElement().getValue()); + } + + @Test + public void testParserFactory() throws JDOMException, IOException { + if (System.getProperty("org.jdom2.performance") == null) { + // for android. + //Assume.assumeNotNull(System.getProperty("org.jdom.performance")); + return; + } + long start = 0L, time = 0L; + loopParser(false, false); + loopParser(false, false); + loopParser(false, false); + start = System.nanoTime(); + loopParser(false, false); + time = System.nanoTime() - start; + System.out.printf("SimpleLoop Recreate %.3fms\n", time / 1000000.0); + + loopParser(true, false); + loopParser(true, false); + loopParser(true, false); + start = System.nanoTime(); + loopParser(true, false); + time = System.nanoTime() - start; + System.out.printf("SimpleLoop Reuse %.3fms\n", time / 1000000.0); + + loopParser(true, true); + loopParser(true, true); + loopParser(true, true); + start = System.nanoTime(); + loopParser(true, true); + time = System.nanoTime() - start; + System.out.printf("SimpleLoop Fast %.3fms\n", time / 1000000.0); + } + + + @SuppressWarnings("deprecation") + private void loopParser(boolean reuse, boolean fast) throws JDOMException, IOException { + if (fast) { + System.out.println("Fast no longer means anything."); + } + SAXBuilder builderval = new SAXBuilder(true); + SAXBuilder buildernoval = new SAXBuilder(false); + builderval.setReuseParser(reuse); + buildernoval.setReuseParser(reuse); + String docstr = + "]>"; + char[] chars = docstr.toCharArray(); + ResetReader rr = new ResetReader(chars); + for (int i = 0; i < 10000; i++) { + parseMem(builderval, rr); + parseMem(buildernoval, rr); + } + //JAXPFastParserFactory.printTimes(); + } + + private void parseMem(SAXBuilder builder, ResetReader reader) throws JDOMException, IOException { + reader.reset(); + Document doc = builder.build(reader); + assertTrue(doc.hasRootElement()); + assertEquals("root", doc.getRootElement().getName()); + } + + private static final class ResetReader extends Reader { + + private final char[] chars; + private int pos = 0; + + public ResetReader(final char[] ch) { + chars = ch; + } + + @Override + public int read(final CharBuffer target) throws IOException { + final int got = chars.length - pos; + if (got == 0) { + return -1; + } + final int howmuch = target.remaining(); + final int ret = got > howmuch ? howmuch : got; + target.put(chars, pos, ret); + pos += ret; + return ret; + } + + @Override + public int read() throws IOException { + if (pos >= chars.length) { + return -1; + } + return chars[pos++]; + } + + @Override + public int read(final char[] cbuf) throws IOException { + final int got = chars.length - pos; + if (got == 0) { + return -1; + } + final int ret = got > cbuf.length ? cbuf.length : got; + System.arraycopy(chars, pos, cbuf, 0, ret); + pos += ret; + return ret; + } + + @Override + public int read(final char[] cbuf, final int off, final int howmuch) throws IOException { + final int got = chars.length - pos; + if (got == 0) { + return -1; + } + final int ret = got > howmuch ? howmuch : got; + System.arraycopy(chars, pos, cbuf, off, ret); + pos += ret; + return ret; + } + + @Override + public long skip(final long n) throws IOException { + long got = chars.length - pos; + if (got > n) { + pos += (int)n; + return n; + } + long ret = chars.length - pos; + pos = chars.length; + return ret; + } + + @Override + public boolean ready() throws IOException { + return true; + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public void mark(final int readAheadLimit) throws IOException { + return; + } + + @Override + public void reset() throws IOException { + pos = 0; + } + + @Override + public void close() throws IOException { + return; + } + + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestSAXComplexSchema.java b/test/src/java/org/jdom/test/cases/input/TestSAXComplexSchema.java new file mode 100644 index 0000000..321f2fe --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestSAXComplexSchema.java @@ -0,0 +1,124 @@ +/** + * + */ +package org.jdom.test.cases.input; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.StringWriter; +import java.net.URL; +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; + +import org.jdom.Attribute; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaders; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.FidoFetch; + + +/** + * @author Rolf Lear + * + */ +@SuppressWarnings("javadoc") +public class TestSAXComplexSchema { + + + /** + * Test method for {@link org.jdom.input.SAXBuilder#build(java.io.File)}. + */ + @SuppressWarnings("deprecation") + @Test + public void testBuildFileOldWay() throws IOException { + SAXBuilder builder = new SAXBuilder(true); + builder.setFeature("http://xml.org/sax/features/namespaces", true); + builder.setFeature("http://xml.org/sax/features/namespace-prefixes", true); + builder.setFeature("http://apache.org/xml/features/validation/schema", true); + + URL rurl = FidoFetch.getFido().getURL("/xsdcomplex/input.xml"); + + + try { + Document doc = builder.build(rurl); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + StringWriter sw = new StringWriter(); + out.output(doc, sw); + assertTrue(sw.toString().length() > 0); + //System.out.println("Document parsed. Content:\n" + xml + "\n"); + + Namespace defns = Namespace.getNamespace("http://www.jdom.org/tests/default"); + Namespace impns = Namespace.getNamespace("http://www.jdom.org/tests/imp"); + + Element root = doc.getRootElement(); + assertTrue(root != null); + assertTrue("test".equals(root.getName())); + List kids = root.getChildren("data", defns); + for (Iterator it = kids.iterator(); it.hasNext(); ) { + Element data = it.next(); + assertTrue(defns.equals(data.getNamespace())); + Attribute att = data.getAttribute("type", Namespace.NO_NAMESPACE); + assertTrue("Could not find type attribute in default ns.", att != null); + att = data.getAttribute("type", impns); + assertTrue("Could not find type attribute in impns.", att != null); + } + } catch (JDOMException e) { + e.printStackTrace(); + fail("Parsing failed. See stack trace."); + } + + } + + /** + * Test method for {@link org.jdom.input.SAXBuilder#build(java.io.File)}. + */ + @Test + public void testBuildFileNewSAX() throws IOException { + SAXBuilder builder = new SAXBuilder(XMLReaders.XSDVALIDATING); + + URL rurl = FidoFetch.getFido().getURL("/xsdcomplex/input.xml"); + + + try { + Document doc = builder.build(rurl); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + StringWriter sw = new StringWriter(); + out.output(doc, sw); + assertTrue(sw.toString().length() > 0); + //System.out.println("Document parsed. Content:\n" + xml + "\n"); + + Namespace defns = Namespace.getNamespace("http://www.jdom.org/tests/default"); + Namespace impns = Namespace.getNamespace("http://www.jdom.org/tests/imp"); + + Element root = doc.getRootElement(); + assertTrue(root != null); + assertTrue("test".equals(root.getName())); + List kids = root.getChildren("data", defns); + for (Iterator it = kids.iterator(); it.hasNext(); ) { + Element data = it.next(); + assertTrue(defns.equals(data.getNamespace())); + Attribute att = data.getAttribute("type", Namespace.NO_NAMESPACE); + assertTrue("Could not find type attribute in default ns.", att != null); + assertTrue(att.isSpecified()); + att = data.getAttribute("type", impns); + assertTrue("Could not find type attribute in impns.", att != null); + assertFalse(att.isSpecified()); + } + } catch (JDOMException e) { + e.printStackTrace(); + fail("Parsing failed. See stack trace."); + } + + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestSAXHandler.java b/test/src/java/org/jdom/test/cases/input/TestSAXHandler.java new file mode 100644 index 0000000..effd2e6 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestSAXHandler.java @@ -0,0 +1,1566 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.NoSuchElementException; + +import javax.xml.XMLConstants; + +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.ext.Attributes2; +import org.xml.sax.helpers.LocatorImpl; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.filter.ContentFilter; +import org.jdom.input.sax.SAXHandler; + +@SuppressWarnings("javadoc") +public class TestSAXHandler { + + private static final class AttributesSingleOnly implements Attributes2 { + + private final String uri, localName, qName, type, value; + + public AttributesSingleOnly(String uri, String localName, String qName, String type, String value) { + this.uri = uri; + this.localName = localName; + this.qName = qName; + this.type = type; + this.value = value; + } + + private final boolean areEquals(Object a, Object b) { + if (a == null && b == null) { + return true; + } + if (a != null) { + return a.equals(b); + } + return false; + } + + @Override + public int getIndex(String puri, String plocalName) { + return areEquals(uri, puri) && areEquals(localName, plocalName) ? + 0 : -1; + } + + @Override + public int getIndex(String pqName) { + return areEquals(qName, pqName) ? 0 : -1; + } + + @Override + public int getLength() { + return 1; + } + + @Override + public String getLocalName(int index) { + if (index == 0) { + return localName; + } + throw new NoSuchElementException(); + } + + @Override + public String getQName(int index) { + if (index == 0) { + return qName; + } + throw new NoSuchElementException(); + } + + @Override + public String getType(int index) { + if (index == 0) { + return type; + } + throw new NoSuchElementException(); + } + + @Override + public String getType(String puri, String plocalName) { + return getType(getIndex(puri, plocalName)); + } + + @Override + public String getType(String pqName) { + return getType(getIndex(pqName)); + } + + @Override + public String getURI(int index) { + if (index == 0) { + return uri; + } + throw new NoSuchElementException(); + } + + @Override + public String getValue(int index) { + if (index == 0) { + return value; + } + throw new NoSuchElementException(); + } + + @Override + public String getValue(String puri, String plocalName) { + return getValue(getIndex(puri, plocalName)); + } + + @Override + public String getValue(String pqName) { + return getType(getIndex(pqName)); + } + + @Override + public boolean isDeclared(int index) { + if (index == 0) { + return true; + } + throw new NoSuchElementException(); + } + + @Override + public boolean isDeclared(String puri, String plocalName) { + return isDeclared(getIndex(puri, plocalName)); + } + + @Override + public boolean isDeclared(String pqName) { + return isDeclared(getIndex(pqName)); + } + + @Override + public boolean isSpecified(int index) { + if (index == 0) { + return true; + } + throw new NoSuchElementException(); + } + + @Override + public boolean isSpecified(String puri, String plocalName) { + return isSpecified(getIndex(puri, plocalName)); + } + + @Override + public boolean isSpecified(String pqName) { + return isSpecified(getIndex(pqName)); + } + + } + + private class MyHandler extends SAXHandler { + private MyHandler () { + super(); + } + @Override + public void pushElement(Element element) { + super.pushElement(element); + } + } + + private static final Attributes2 EMPTYATTRIBUTES = new org.xml.sax.ext.Attributes2Impl(); + + private static final void assertMatches(String pattern, String value) { + assertTrue("Pattern for assertMatches is null", pattern != null); + assertTrue("Value for assertMatches is null", value != null); + if (!value.matches(pattern)) { + fail("Value '" + value + "' does not match pattern '" + pattern +"."); + } + } + + private abstract class Builder { + public SAXHandler createHandler() { + return new SAXHandler(); + } + + public abstract void build(SAXHandler handler) throws SAXException; + } + + private static final Document checkHandlerDocument(Builder cd) { + try { + SAXHandler handler = cd.createHandler(); + handler.startDocument(); + cd.build(handler); + handler.endDocument(); + return handler.getDocument(); + } catch (SAXException se) { + se.printStackTrace(); + fail("Failed TestSAXHandler with SAXException: " + se.getMessage()); + } + return null; + + } + + private static final Element checkHandlerElement(Builder cd) { + try { + SAXHandler handler = cd.createHandler(); + handler.startDocument(); + handler.startElement("", "root", "root", EMPTYATTRIBUTES); + cd.build(handler); + handler.endElement("", "root", "root"); + handler.endDocument(); + return handler.getDocument().getRootElement(); + } catch (SAXException se) { + se.printStackTrace(); + fail("Failed TestSAXHandler with SAXException: " + se.getMessage()); + } + return null; + + } + + private static final String checkHandlerDTDInternalSubset(Builder cd) { + try { + SAXHandler handler = cd.createHandler(); + handler.startDocument(); + handler.startDTD("root", "publicID", "systemID"); + cd.build(handler); + handler.endDTD(); + handler.endDocument(); + return handler.getDocument().getDocType().getInternalSubset().trim(); //.replaceAll("(^\\s*<\\s*)|(\\s*>\\s*$)", ""); + } catch (SAXException se) { + se.printStackTrace(); + fail("Failed TestSAXHandler with SAXException: " + se.getMessage()); + } + return null; + + } + + @Test + public void testDocument() { + Document doc = null; + + doc = checkHandlerDocument(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + // do nothing. + } + }); + assertTrue(doc.getDocType() == null); + assertFalse(doc.hasRootElement()); + + final JDOMFactory deffac = new DefaultJDOMFactory(); + doc = checkHandlerDocument(new Builder() { + @Override + public SAXHandler createHandler() { + return new SAXHandler(deffac); + } + @Override + public void build(SAXHandler handler) throws SAXException { + assertTrue(deffac == handler.getFactory()); + } + }); + assertTrue(doc.getDocType() == null); + assertFalse(doc.hasRootElement()); + + doc = checkHandlerDocument(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startDTD("dtdname", "publicID", "systemID"); + handler.endDTD(); + } + }); + assertFalse(doc.hasRootElement()); + assertTrue(doc.getDocType() != null); + assertEquals("dtdname", doc.getDocType().getElementName()); + + doc = checkHandlerDocument(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startDTD("dtdname", "publicID", "systemID"); + handler.endDTD(); + handler.comment("comment".toCharArray(), 2, 2); + } + }); + assertFalse(doc.hasRootElement()); + assertTrue(doc.getDocType() != null); + assertEquals("dtdname", doc.getDocType().getElementName()); + assertTrue(doc.getContent(1) instanceof Comment); + assertEquals("mm", ((Comment)doc.getContent(1)).getText()); + + doc = checkHandlerDocument(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startDTD("dtdname", "publicID", "systemID"); + handler.endDTD(); + handler.comment("comment".toCharArray(), 2, 2); + handler.startElement("", "root", "", EMPTYATTRIBUTES); + } + }); + assertTrue(doc.hasRootElement()); + assertTrue(doc.getDocType() != null); + assertEquals("dtdname", doc.getDocType().getElementName()); + assertTrue(doc.getContent(1) instanceof Comment); + assertEquals("mm", ((Comment)doc.getContent(1)).getText()); + assertTrue(doc.getContent(2) instanceof Element); + assertEquals("root", ((Element)doc.getContent(2)).getName()); + + final LocatorImpl loc = new LocatorImpl(); + loc.setSystemId("baseURL"); + final SAXHandler handler = new SAXHandler(); + handler.setDocumentLocator(loc); + doc = checkHandlerDocument(new Builder() { + @Override + public SAXHandler createHandler() { + return handler; + } + @Override + public void build(SAXHandler phandler) throws SAXException { + phandler.startDTD("dtdname", "publicID", "systemID"); + phandler.endDTD(); + phandler.comment("comment".toCharArray(), 2, 2); + phandler.startElement("", "root", "", EMPTYATTRIBUTES); + } + }); + assertTrue(doc.hasRootElement()); + assertTrue(doc.getDocType() != null); + assertEquals("dtdname", doc.getDocType().getElementName()); + assertTrue(doc.getContent(1) instanceof Comment); + assertEquals("mm", ((Comment)doc.getContent(1)).getText()); + assertTrue(doc.getContent(2) instanceof Element); + assertEquals("root", ((Element)doc.getContent(2)).getName()); + assertEquals("baseURL", doc.getBaseURI()); + assertTrue(loc == handler.getDocumentLocator()); + + } + + @Test + public void testExpandEntities() { + SAXHandler handler = new SAXHandler(); + assertTrue(handler.getExpandEntities()); + handler.setExpandEntities(true); + assertTrue(handler.getExpandEntities()); + handler.setExpandEntities(false); + assertFalse(handler.getExpandEntities()); + handler.setExpandEntities(true); + assertTrue(handler.getExpandEntities()); + } + + @Test + public void testIgnoringElementContentWhitespace() { + SAXHandler handler = new SAXHandler(); + assertFalse(handler.getIgnoringElementContentWhitespace()); + handler.setIgnoringElementContentWhitespace(true); + assertTrue(handler.getIgnoringElementContentWhitespace()); + handler.setIgnoringElementContentWhitespace(false); + assertFalse(handler.getIgnoringElementContentWhitespace()); + handler.setIgnoringElementContentWhitespace(true); + assertTrue(handler.getIgnoringElementContentWhitespace()); + } + + @Test + public void testIgnoringBoundaryWhitespace() { + SAXHandler handler = new SAXHandler(); + assertFalse(handler.getIgnoringBoundaryWhitespace()); + handler.setIgnoringBoundaryWhitespace(true); + assertTrue(handler.getIgnoringBoundaryWhitespace()); + handler.setIgnoringBoundaryWhitespace(false); + assertFalse(handler.getIgnoringBoundaryWhitespace()); + handler.setIgnoringBoundaryWhitespace(true); + assertTrue(handler.getIgnoringBoundaryWhitespace()); + } + + + /* ********************************** + * LexicalHandler method tests. + * **********************************/ + @Test + public void testDTD() { + Document doc = checkHandlerDocument(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startDTD("root", "publicID", "systemID"); + // bunch of things for use during a DTD. + handler.elementDecl("root", "model"); + handler.attributeDecl("root", "att", "UNKNOWN", "", ""); + handler.externalEntityDecl("extent", "publicID", "systemID"); + handler.comment("foo".toCharArray(), 0, 3); + handler.internalEntityDecl("intent", "value"); + handler.endDTD(); + } + }); + + assertEquals("root", doc.getDocType().getElementName()); + assertEquals("publicID", doc.getDocType().getPublicID()); + assertEquals("systemID", doc.getDocType().getSystemID()); + } + + @Test + public void testEntity() { + // with expandEntities set to true, we lose the entity during parsing + assertTrue( + checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(true); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("entity"); + handler.endEntity("entity"); + } + }).getContentSize() == 0); + + // with expandEntities set to false, we expect an entity + assertMatches(".*&entity;.*", + checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("entity"); + handler.endEntity("entity"); + } + }).getContent(0).toString()); + + // with [dtd] we expect nothing + assertTrue( + checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("[dtd]"); + handler.endEntity("[dtd]"); + } + }).getContentSize() == 0); + + // 5 standard entities should be ignored. + assertTrue( + checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("amp"); + handler.endEntity("amp"); + + handler.startEntity("apos"); + handler.endEntity("apos"); + + handler.startEntity("quot"); + handler.endEntity("quot"); + + handler.startEntity("gt"); + handler.endEntity("gt"); + + handler.startEntity("lt"); + handler.endEntity("lt"); + } + }).getContentSize() == 0); + + // with expandEntities set to false, we expect an entity + EntityRef ent = (EntityRef)checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.externalEntityDecl("entity", "publicID", "systemID"); + handler.startEntity("entity"); + handler.endEntity("entity"); + } + }).getContent(0); + assertEquals("entity", ent.getName()); + assertEquals("publicID", ent.getPublicID()); + assertEquals("systemID", ent.getSystemID()); + + // when processing an entity, we should ignore all other event types. + Element emt = checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("entity"); + handler.startPrefixMapping("prefix", "uri"); + handler.startElement("", "ignore", "", EMPTYATTRIBUTES); + handler.endElement("", "ignore", ""); + handler.endPrefixMapping("prefix"); + handler.processingInstruction("target", "data"); + handler.comment("ignore".toCharArray(), 0, 6); + handler.characters("ignore".toCharArray(), 0, 6); + handler.characters("ignore".toCharArray(), 0, 0); + handler.ignorableWhitespace(" ".toCharArray(), 0, 2); + handler.startCDATA(); + handler.characters("ignore".toCharArray(), 0, 6); + handler.endCDATA(); + handler.endEntity("entity"); + } + }); + // everything should have been ignored because of the startEntity() + // which just leaves the actual entity itself. + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof EntityRef); + EntityRef ent2 = (EntityRef)emt.getContent(0); + assertEquals("entity", ent2.getName()); + + // when processing an entity, we should ignore all other event types. + Element emt2 = checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("entity"); + // nested should be ignored. + handler.startEntity("nested"); + handler.endEntity("nested"); + handler.endEntity("entity"); + } + }); + // everything should have been ignored because of the startEntity() + // which just leaves the actual entity itself. + assertTrue(emt2.getContentSize() == 1); + assertTrue(emt2.getContent(0) instanceof EntityRef); + EntityRef ent3 = (EntityRef)emt2.getContent(0); + assertEquals("entity", ent3.getName()); + + // when outside the roo element should be ignored... + Document doc = checkHandlerDocument(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("entity"); + handler.endEntity("entity"); + } + }); + // everything should have been ignored because of the startEntity() + // which just leaves the actual entity itself. + assertTrue(doc.getContentSize() == 0); + + } + + @Test + public void testCDATA() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.comment("foo".toCharArray(), 0, 3); + handler.characters(" ".toCharArray(), 0, 2); + handler.startCDATA(); + handler.characters(" foobar ".toCharArray(), 0, 10); + handler.endCDATA(); + handler.characters(" ".toCharArray(), 0, 2); + } + }); + + assertEquals("foobar", emt.getTextTrim()); + assertEquals(" foobar ", ((CDATA)emt.getContent(new ContentFilter(ContentFilter.CDATA)).get(0)).getText()); + + } + + @Test + public void testComment() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.comment("foo".toCharArray(), 0, 3); + handler.characters(" ".toCharArray(), 0, 2); + handler.startCDATA(); + handler.characters(" foobar ".toCharArray(), 0, 10); + handler.endCDATA(); + handler.characters(" ".toCharArray(), 0, 2); + handler.comment("bar".toCharArray(), 0, 3); + } + }); + + int cnt = 0; + for (Object o : emt.getContent(new ContentFilter(ContentFilter.COMMENT))) { + assertTrue(o instanceof Comment); + switch (cnt++) { + case 0 : + assertEquals("foo", ((Comment)o).getText()); + break; + case 1 : + assertEquals("bar", ((Comment)o).getText()); + break; + default : + fail("Expecting only two comments"); + + } + } + + assertMatches("\\s*\\s*", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("[dtd]"); + handler.unparsedEntityDecl("name", null, "systemID", "notationName"); + handler.endEntity("[dtd]"); + handler.comment("comment".toCharArray(), 0, 7); + } + })); + + } + + + + /* ********************************** + * DeclHandler method tests These should + * all be run between LexicalHandler's + * startDTD and endDTD events. + * **********************************/ + @Test + public void testAttributeDecl() { + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.attributeDecl("root", "att", "", "", ""); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.attributeDecl("root", "att", "", "default", "value"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.attributeDecl("root", "att", "", null, "value"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.attributeDecl("root", "att", "type", "default", "value"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.attributeDecl("root", "att", "type", null, "value"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.attributeDecl("root", "att", "type", "#FIXED", "value"); + } + })); + + } + + @Test + public void testElementDecl() { + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.elementDecl("root", "model"); + } + })); + } + + @Test + public void testInternalEntityDecl() { + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.internalEntityDecl("name", "value"); + } + })); + + //Parameter Entity Declaration + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.internalEntityDecl("%name", "value"); + } + })); + } + + @Test + public void testExternalEntityDecl() { + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.externalEntityDecl("name", "publicID", "systemID"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.externalEntityDecl("name", null, "systemID"); + } + })); + + } + + @Test + public void testUnparsedEntityDecl() { + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.unparsedEntityDecl("name", "publicID", "systemID", "notationName"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.unparsedEntityDecl("name", null, "systemID", "notationName"); + } + })); + assertMatches("\\s*", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startEntity("[dtd]"); + handler.unparsedEntityDecl("name", null, "systemID", "notationName"); + handler.endEntity("[dtd]"); + } + })); + } + + @Test + public void testNotationDecl() { + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.notationDecl("name", "publicID", "systemID"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.notationDecl("name", null, "systemID"); + } + })); + + assertMatches("", + checkHandlerDTDInternalSubset(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.notationDecl("name", "publicID", null); + } + })); + + Document doc = checkHandlerDocument(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler handler = new SAXHandler(); + handler.setExpandEntities(false); + return handler; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startDTD("name", "publicID", "systemID"); + handler.startEntity("[dtd]"); + handler.notationDecl("exdtd", "publicIDA", "systemIDA"); + handler.endEntity("[dtd]"); + handler.notationDecl("indtd", "publicIDB", "systemIDB"); + handler.endDTD(); + } + }); + + DocType dt = doc.getDocType(); + assertTrue(dt != null); + assertMatches("", + dt.getInternalSubset().trim()); + } + + /* ********************************** + * ContentHandler method tests. + * **********************************/ + @Test + public void testProcessingInstruction() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.processingInstruction("target", "data"); + } + }); + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof ProcessingInstruction); + ProcessingInstruction pi = (ProcessingInstruction)emt.getContent(0); + assertEquals("target", pi.getTarget()); + assertEquals("data", pi.getData()); + + Document doc = checkHandlerDocument(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.processingInstruction("target", "data"); + } + }); + assertTrue(doc.getContentSize() == 1); + assertTrue(doc.getContent(0) instanceof ProcessingInstruction); + pi = (ProcessingInstruction)emt.getContent(0); + assertEquals("target", pi.getTarget()); + assertEquals("data", pi.getData()); + + } + + @Test + public void testSkippedEntityString() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + // this one should be ignored. + handler.skippedEntity("%ignore"); + // this one should be added. + handler.skippedEntity("entity"); + } + }); + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof EntityRef); + EntityRef er = (EntityRef)emt.getContent(0); + assertEquals("entity", er.getName()); + } + + @Test + public void testElementSimple() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", EMPTYATTRIBUTES); + handler.endElement("", "child", ""); + } + }); + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + } + + @Test + public void testElementNullURI() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement(null, "child", "", EMPTYATTRIBUTES); + handler.endElement(null, "child", ""); + } + }); + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + } + + @Test + public void testElementBadEndElement() { + try { + SAXHandler handler = new SAXHandler(); + handler.startDocument(); + handler.startElement("", "root", "root", EMPTYATTRIBUTES); + handler.endElement("", "root", "root"); + handler.endElement("", "bad", "bad"); + handler.endDocument(); + fail("Should not have been able to create Element "); + } catch (SAXException se) { + // good + } catch (Exception e) { + fail("Expecting SAXEsception, but got " + e.getClass().getName()); + } + } + + @Test + public void testElementAttributesSimple() { + // simple attribute. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertEquals("val", child.getAttributeValue("att")); + assertEquals(AttributeType.CDATA, child.getAttribute("att").getAttributeType()); + } + + @Test + public void testElementAttributesNameXMLNS() { + // invalid xmlns attibute. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "xmlns", "xmlns", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().isEmpty()); + } + + @Test + public void testElementAttributesPrefixXMLNS() { + // invalid xmlns attibute. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "ns", "xmlns:ns", "CDATA", "uri"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 0); + } + + @Test + public void testElementAttributesNoLocalName() { + // no-localname, but has qname. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertEquals("val", child.getAttributeValue("att")); + } + + + @Test + public void testElementAttributesSimpleInNamespace() { + // normal att-in-namespace. + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "pfx:att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("pfx", "nsuri"); + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + handler.endPrefixMapping("pfx"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Namespace ns = Namespace.getNamespace("pfx", "nsuri"); + assertTrue(child.getAttribute("att", ns) != null); + assertEquals("val", child.getAttributeValue("att", ns)); + } + + + @Test + public void testElementAttributesNoPrefixNamespaceMustGeneratePrefix() { + // weird att-in-namespace - no prefix. + // namespace of parent element matches, but no prefix. + // should invent a prefix (attns0) + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("nsuri", "child", "child", atts); + handler.endElement("nsuri", "child", "child"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("nsuri", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Attribute a = child.getAttribute("att", child.getNamespace()); + assertEquals("attns0", a.getNamespacePrefix()); + assertEquals("nsuri", a.getNamespaceURI()); + assertEquals("val", a.getValue()); + } + + @Test + public void testElementAttributesNoPrefixNamespaceMustGenerateAlternatePrefix() { + // weird att-in-namespace - no prefix. + // namespace of parent element matches, but no prefix. + // also, attns0 is used by some other namespace. + // should invent a prefix (attns1) + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("attns0", "otheruri"); + handler.startElement("nsuri", "child", "child", atts); + handler.endElement("nsuri", "child", "child"); + handler.endPrefixMapping("atns0"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("nsuri", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Attribute a = child.getAttribute("att", child.getNamespace()); + assertEquals("attns1", a.getNamespacePrefix()); + assertEquals("nsuri", a.getNamespaceURI()); + assertEquals("val", a.getValue()); + } + + @Test + public void testElementAttributesNoPrefixNamespaceMustUseParentLevelPrefix() { + // weird att-in-namespace - no prefix. + // but there is a prefix declared for it at the parent level. + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("pfx", "nsuri"); + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + handler.endPrefixMapping("pfx"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Namespace nsd = Namespace.getNamespace("differentprefix", "nsuri"); + Attribute a = child.getAttribute("att", nsd); + assertEquals("pfx", a.getNamespacePrefix()); + assertEquals("nsuri", a.getNamespaceURI()); + assertEquals("val", a.getValue()); + } + + @Test + public void testElementAttributesNoPrefixNamespaceMustUseActualParentPrefix() { + // weird att-in-namespace - no prefix. + // namespace of parent element matches, it has prefix. + // should use parent prefix (pfx) + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("attns0", "otheruri"); + handler.startElement("nsuri", "child", "pfx:child", atts); + handler.endElement("nsuri", "child", "pfx:child"); + handler.endPrefixMapping("atns0"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("pfx", child.getNamespacePrefix()); + assertEquals("nsuri", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Attribute a = child.getAttribute("att", child.getNamespace()); + assertEquals("pfx", a.getNamespacePrefix()); + assertEquals("nsuri", a.getNamespaceURI()); + assertEquals("val", a.getValue()); + } + + + @Test + public void testElementAttributesNoPrefixNamespaceParentPrefixOverrides() { + // weird att-in-namespace - no prefix. + // also, a matching prefix was overridden. + // should generate one (attns0). + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("pfx", "nsuri"); + handler.startElement("nsuri", "middle", "pfx:middle", EMPTYATTRIBUTES); + // re-define the namespace prefix with a different uri + handler.startElement("childuri", "child", "pfx:child", atts); + handler.endElement("childuri", "child", "pfx:child"); + handler.endElement("", "middle", "pfx:middle"); + handler.endPrefixMapping("pfx"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element middle = (Element)emt.getContent(0); + assertEquals("middle", middle.getName()); + assertEquals("pfx", middle.getNamespacePrefix()); + assertEquals("nsuri", middle.getNamespaceURI()); + + Element child = middle.getChild("child", Namespace.getNamespace("childuri")); + assertEquals("child", child.getName()); + assertEquals("pfx", child.getNamespacePrefix()); + assertEquals("childuri", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Attribute a = child.getAttribute("att", middle.getNamespace()); + assertEquals("attns0", a.getNamespacePrefix()); + assertEquals("nsuri", a.getNamespaceURI()); + assertEquals("val", a.getValue()); + } + + @Test + public void testElementAttributesNoPrefixNamespaceParentPrefixLevelOverrides() { + // weird att-in-namespace - no prefix. + // also, a matching prefix at the parent level was overridden. + // should generate one (attns0). + final AttributesSingleOnly atts = new AttributesSingleOnly("nsuri", "att", "att", "CDATA", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("pfx", "nsuri"); + handler.startElement("nsuri", "middle", "pfx:middle", EMPTYATTRIBUTES); + // re-define the namespace prefix with a different uri + handler.startPrefixMapping("pfx", "ignoreuri"); + handler.startElement("childuri", "child", "kid:child", atts); + handler.endElement("childuri", "child", "kid:child"); + handler.endPrefixMapping("pfx"); + handler.endElement("", "middle", "pfx:middle"); + handler.endPrefixMapping("pfx"); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element middle = (Element)emt.getContent(0); + assertEquals("middle", middle.getName()); + assertEquals("pfx", middle.getNamespacePrefix()); + assertEquals("nsuri", middle.getNamespaceURI()); + + Element child = middle.getChild("child", Namespace.getNamespace("childuri")); + assertEquals("child", child.getName()); + assertEquals("kid", child.getNamespacePrefix()); + assertEquals("childuri", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertTrue(child.getAttribute("att") == null); + Attribute a = child.getAttribute("att", middle.getNamespace()); + assertEquals("attns0", a.getNamespacePrefix()); + assertEquals("nsuri", a.getNamespaceURI()); + assertEquals("val", a.getValue()); + } + + + @Test + public void testElementAttributesTypeIsEnumeration() { + // simple attribute. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "att", "att", "(val1,val2)", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + assertEquals("val", child.getAttributeValue("att")); + } + + @Test + public void testElementAttributesTypeIsUnknown() { + // simple attribute. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "att", "att", "poppygook", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + Attribute att = child.getAttribute("att"); + assertEquals("val", att.getValue()); + assertEquals(AttributeType.UNDECLARED, att.getAttributeType()); + } + + @Test + public void testElementAttributesTypeIsNull() { + // null attribute type. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "att", "att", null, "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + Attribute att = child.getAttribute("att"); + assertEquals("val", att.getValue()); + assertEquals(AttributeType.UNDECLARED, att.getAttributeType()); + } + + + @Test + public void testElementAttributesTypeIsEmptyString() { + // "" attribute type. + final AttributesSingleOnly atts = new AttributesSingleOnly("", "att", "att", "", "val"); + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startElement("", "child", "", atts); + handler.endElement("", "child", ""); + } + }); + + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("", child.getNamespacePrefix()); + assertEquals("", child.getNamespaceURI()); + assertTrue(child.getAttributes().size() == 1); + Attribute att = child.getAttribute("att"); + assertEquals("val", att.getValue()); + assertEquals(AttributeType.UNDECLARED, att.getAttributeType()); + } + + + @Test + public void testPrefixMapping() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.startPrefixMapping("prefix", "uri"); + handler.startElement("uri", "child", "prefix:uri", EMPTYATTRIBUTES); + handler.endElement("uri", "child", "prefix:uri"); + handler.endPrefixMapping("prefix"); + } + }); + assertTrue(emt.getContentSize() == 1); + assertTrue(emt.getContent(0) instanceof Element); + Element child = (Element)emt.getContent(0); + assertEquals("child", child.getName()); + assertEquals("prefix", child.getNamespacePrefix()); + assertEquals("uri", child.getNamespaceURI()); + } + + @Test + public void testCharacters() { + Element emt = checkHandlerElement(new Builder() { + @Override + public void build(SAXHandler handler) throws SAXException { + handler.comment("foo".toCharArray(), 0, 3); + handler.characters(" ".toCharArray(), 0, 2); + // 0-length should be ignored. + handler.characters("xxxx".toCharArray(), 0, 0); + handler.characters(" foobar ".toCharArray(), 0, 10); + handler.characters(" ".toCharArray(), 0, 2); + } + }); + + assertEquals(" foobar ", emt.getText()); + assertEquals("foobar", emt.getTextTrim()); + } + + @Test + public void testIgnorableWhitespaceTrue() { + Element emt = checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler ret = new SAXHandler(); + ret.setIgnoringElementContentWhitespace(true); + return ret; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.comment("foo".toCharArray(), 0, 3); + // 3 chars, length 2 though. + handler.ignorableWhitespace(" ".toCharArray(), 0, 2); + handler.characters(" foobar ".toCharArray(), 0, 10); + // 0 length + handler.ignorableWhitespace(" ".toCharArray(), 0, 0); + handler.ignorableWhitespace(" ".toCharArray(), 0, 2); + } + }); + + assertEquals(" foobar ", emt.getText()); + assertEquals("foobar", emt.getTextTrim()); + } + + @Test + public void testIgnorableBoundaryWhitespaceTrue() { + assertEquals("", checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler ret = new SAXHandler(); + ret.setIgnoringBoundaryWhitespace(true); + return ret; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.characters(" \n\n ".toCharArray(), 0, 6); + } + }).getText()); + + assertEquals(" \nx\n ", checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler ret = new SAXHandler(); + ret.setIgnoringBoundaryWhitespace(true); + return ret; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.characters(" \nx\n ".toCharArray(), 0, 5); + } + }).getText()); + + } + + @Test + public void testIgnorableWhitespaceFalse() { + Element emt = checkHandlerElement(new Builder() { + @Override + public SAXHandler createHandler() { + SAXHandler ret = new SAXHandler(); + ret.setIgnoringElementContentWhitespace(false); + return ret; + } + @Override + public void build(SAXHandler handler) throws SAXException { + handler.comment("foo".toCharArray(), 0, 3); + handler.ignorableWhitespace(" ".toCharArray(), 0, 2); + handler.characters(" foobar ".toCharArray(), 0, 10); + handler.ignorableWhitespace(" ".toCharArray(), 0, 2); + } + }); + + assertEquals(" foobar ", emt.getText()); + assertEquals("foobar", emt.getTextTrim()); + } + + @Test + public void testPushElement() { + try { + MyHandler handler = new MyHandler(); + handler.startDocument(); + handler.pushElement(new Element("root")); + handler.endDocument(); + Document doc = handler.getDocument(); + assertTrue(doc.hasRootElement()); + assertEquals("root", doc.getRootElement().getName()); + } catch (SAXException se) { + se.printStackTrace(); + fail ("Failed to load MyHandler: " + se.getMessage()); + } + try { + MyHandler handler = new MyHandler(); + handler.startDocument(); + handler.startElement("", "root", "root", EMPTYATTRIBUTES); + handler.pushElement(new Element("child")); + handler.endElement("", "root", "root"); + handler.endDocument(); + Document doc = handler.getDocument(); + assertTrue(doc.hasRootElement()); + assertEquals("root", doc.getRootElement().getName()); + assertTrue(doc.getRootElement().getChild("child") != null); + } catch (SAXException se) { + se.printStackTrace(); + fail ("Failed to load MyHandler: " + se.getMessage()); + } + } + + @Test + public void testIllegalGetCurrentElement() { + SAXHandler handler = new SAXHandler(); + handler.startDocument(); + try { + handler.getCurrentElement(); + fail ("Should not be able to append bad element strcuture."); + } catch (SAXException se) { + // good/. + } catch (Exception e) { + fail("Expected to get SAXException but instead got " + e.getClass().getName() + "'."); + } + + } + + @Test + public void testAndroidParserIssue2LocalOnly() throws SAXException { + SAXHandler handler = new SAXHandler(); + handler.startDocument(); + AttributesSingleOnly attrs = new AttributesSingleOnly("", "attname", "", "CDATA", "val"); + handler.startElement("", "root", "", attrs); + handler.endElement("", "root", ""); + handler.endDocument(); + Document doc = handler.getDocument(); + Element root = doc.getRootElement(); + assertEquals("root", root.getName()); + Attribute att = root.getAttribute("attname"); + assertNotNull(att); + assertEquals("val", att.getValue()); + } + + @Test + public void testAndroidParserIssue2QNameOnly() throws SAXException { + SAXHandler handler = new SAXHandler(); + handler.startDocument(); + AttributesSingleOnly attrs = new AttributesSingleOnly("", "", "attname", "CDATA", "val"); + handler.startElement("", "", "root", attrs); + handler.endElement("", "root", ""); + handler.endDocument(); + Document doc = handler.getDocument(); + Element root = doc.getRootElement(); + assertEquals("root", root.getName()); + Attribute att = root.getAttribute("attname"); + assertNotNull(att); + assertEquals("val", att.getValue()); + } + + @Test + public void testAndroidParserIssue2AttXMLNS() throws SAXException { + // test a namespace-aware parser that is not configured namespace-prefixes + // in theory, it should leave the qName empty, even for an XMLNS declaration + SAXHandler handler = new SAXHandler(); + handler.startDocument(); + AttributesSingleOnly attrs = new AttributesSingleOnly(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, "pfx", "", "CDATA", "nsuri"); + handler.startElement("", "", "root", attrs); + handler.endElement("", "root", ""); + handler.endDocument(); + Document doc = handler.getDocument(); + Element root = doc.getRootElement(); + assertEquals("root", root.getName()); + Attribute att = root.getAttribute("pfx"); + assertNull(att); + assertTrue(root.getAttributes().isEmpty()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestStAXEventBuilder.java b/test/src/java/org/jdom/test/cases/input/TestStAXEventBuilder.java new file mode 100644 index 0000000..94ec1c6 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestStAXEventBuilder.java @@ -0,0 +1,195 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.InputStream; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; + +import org.junit.Ignore; +import org.junit.Test; + +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; +import org.jdom.input.StAXEventBuilder; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestStAXEventBuilder { + + @Test + public void testStAXBuilder() { + StAXEventBuilder db = new StAXEventBuilder(); + assertNotNull(db); + } + + @Test + public void testFactory() { + StAXEventBuilder db = new StAXEventBuilder(); + assertTrue(db.getFactory() instanceof DefaultJDOMFactory); + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertFalse(db.getFactory() == fac); + db.setFactory(fac); + assertTrue(db.getFactory() == fac); + } + + @Test + public void testSimpleDocumentExpand() { + checkStAX("/DOMBuilder/simple.xml", true); + } + + @Test + public void testAttributesDocumentExpand() { + checkStAX("/DOMBuilder/attributes.xml", true); + } + + @Test + public void testNamespaceDocumentExpand() { + checkStAX("/DOMBuilder/namespaces.xml", true); + } + + @Test + @Ignore + // TODO + public void testDocTypeDocumentExpand() { + checkStAX("/DOMBuilder/doctype.xml", true); + } + + @Test + public void testDocTypeDocumentSimpleExpand() { + checkStAX("/DOMBuilder/doctypesimple.xml", true); + } + + @Test + public void testComplexDocumentExpand() { + checkStAX("/DOMBuilder/complex.xml", true); + } + + @Test + public void testXSDDocumentExpand() { + checkStAX("/xsdcomplex/input.xml", true); + } + + @Test + public void testSimpleDocument() { + checkStAX("/DOMBuilder/simple.xml", false); + } + + @Test + public void testAttributesDocument() { + checkStAX("/DOMBuilder/attributes.xml", false); + } + + @Test + public void testNamespaceDocument() { + checkStAX("/DOMBuilder/namespaces.xml", false); + } + + @Test + public void testDocTypeDocument() { + checkStAX("/DOMBuilder/doctype.xml", false); + } + + @Test + public void testDocTypeSimpleDocument() { + checkStAX("/DOMBuilder/doctypesimple.xml", false); + } + + @Test + public void testComplexDocument() { + checkStAX("/DOMBuilder/complex.xml", false); + } + + @Test + public void testXSDDocument() { + checkStAX("/xsdcomplex/input.xml", false); + } + + private void checkStAX(String resourcename, boolean expand) { + try { + StAXEventBuilder stxb = new StAXEventBuilder(); + XMLInputFactory inputfac = XMLInputFactory.newInstance(); + inputfac.setProperty( + "javax.xml.stream.isReplacingEntityReferences", Boolean.valueOf(expand)); + inputfac.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE); + + InputStream eventsource = FidoFetch.getFido().getStream(resourcename); + XMLEventReader events = inputfac.createXMLEventReader(eventsource); + Document eventbuild = stxb.build(events); + Element eventroot = eventbuild.hasRootElement() ? eventbuild.getRootElement() : null; + + SAXBuilder sb = new SAXBuilder(); + sb.setExpandEntities(expand); + + Document saxbuild = sb.build(FidoFetch.getFido().getStream(resourcename)); + Element saxroot = saxbuild.hasRootElement() ? saxbuild.getRootElement() : null; + + assertEquals("DOC SAX to StAXEvent", toString(saxbuild), toString(eventbuild)); + assertEquals("ROOT SAX to StAXEvent", toString(saxroot), toString(eventroot)); + + } catch (Exception e) { + e.printStackTrace(); + fail("Could not parse resource '" + resourcename + "': " + e.getMessage()); + } + } + + private void normalizeDTD(DocType dt) { + if (dt == null) { + return; + } + // do some tricks so that we can compare the results. + // these may well break the actual syntax of DTD's but for testing + // purposes it is OK. + String internalss = dt.getInternalSubset().trim() ; + // the spaceing in and around the internal subset is different between + // our SAX parse, and the DOM parse. + // make all whitespace a single space. + internalss = internalss.replaceAll("\\s+", " "); + // It seems the DOM parser internally quotes entities with single quote + // but our sax parser uses double-quote. + // simply replace all " with ' and be done with it. + internalss = internalss.replaceAll("\"", "'"); + dt.setInternalSubset("\n" + internalss + "\n"); + } + + private String toString(Document doc) { + UnitTestUtil.normalizeAttributes(doc.getRootElement()); + normalizeDTD(doc.getDocType()); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + CharArrayWriter caw = new CharArrayWriter(); + try { + out.output(doc, caw); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return caw.toString(); + } + + private String toString(Element emt) { + UnitTestUtil.normalizeAttributes(emt); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + CharArrayWriter caw = new CharArrayWriter(); + try { + out.output(emt, caw); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return caw.toString(); + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/TestStAXStreamBuilder.java b/test/src/java/org/jdom/test/cases/input/TestStAXStreamBuilder.java new file mode 100644 index 0000000..411eb83 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/TestStAXStreamBuilder.java @@ -0,0 +1,201 @@ +package org.jdom.test.cases.input; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.CharArrayWriter; +import java.io.IOException; +import java.util.List; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamReader; + +import org.junit.Ignore; +import org.junit.Test; + +import org.jdom.Content; +import org.jdom.DefaultJDOMFactory; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.input.SAXBuilder; +import org.jdom.input.StAXStreamBuilder; +import org.jdom.input.stax.DefaultStAXFilter; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestStAXStreamBuilder { + + @Test + public void testStAXBuilder() { + StAXStreamBuilder db = new StAXStreamBuilder(); + assertNotNull(db); + } + + @Test + public void testFactory() { + StAXStreamBuilder db = new StAXStreamBuilder(); + assertTrue(db.getFactory() instanceof DefaultJDOMFactory); + DefaultJDOMFactory fac = new DefaultJDOMFactory(); + assertFalse(db.getFactory() == fac); + db.setFactory(fac); + assertTrue(db.getFactory() == fac); + } + + @Test + public void testSimpleDocumentExpand() { + checkStAX("/DOMBuilder/simple.xml", true); + } + + @Test + public void testAttributesDocumentExpand() { + checkStAX("/DOMBuilder/attributes.xml", true); + } + + @Test + public void testNamespaceDocumentExpand() { + checkStAX("/DOMBuilder/namespaces.xml", true); + } + + @Test + @Ignore + public void testDocTypeDocumentExpand() { + checkStAX("/DOMBuilder/doctype.xml", true); + } + + @Test + @Ignore + public void testDocTypeDocumentSimpleExpand() { + checkStAX("/DOMBuilder/doctypesimple.xml", true); + } + + @Test + public void testComplexDocumentExpand() { + checkStAX("/DOMBuilder/complex.xml", true); + } + + @Test + public void testXSDDocumentExpand() { + checkStAX("/xsdcomplex/input.xml", true); + } + + @Test + public void testSimpleDocument() { + checkStAX("/DOMBuilder/simple.xml", false); + } + + @Test + public void testAttributesDocument() { + checkStAX("/DOMBuilder/attributes.xml", false); + } + + @Test + public void testNamespaceDocument() { + checkStAX("/DOMBuilder/namespaces.xml", false); + } + + @Test + public void testDocTypeDocument() { + checkStAX("/DOMBuilder/doctype.xml", false); + } + + @Test + public void testDocTypeSimpleDocument() { + checkStAX("/DOMBuilder/doctypesimple.xml", false); + } + + @Test + public void testComplexDocument() { + checkStAX("/DOMBuilder/complex.xml", false); + } + + @Test + public void testXSDDocument() { + checkStAX("/xsdcomplex/input.xml", false); + } + + private void checkStAX(String resname, boolean expand) { + try { + StAXStreamBuilder stxb = new StAXStreamBuilder(); + XMLInputFactory inputfac = XMLInputFactory.newInstance(); + inputfac.setProperty( + "javax.xml.stream.isReplacingEntityReferences", Boolean.valueOf(expand)); + inputfac.setProperty("http://java.sun.com/xml/stream/properties/report-cdata-event", Boolean.TRUE); + XMLStreamReader reader = inputfac.createXMLStreamReader(FidoFetch.getFido().getStream(resname)); + Document staxbuild = stxb.build(reader); + Element staxroot = staxbuild.hasRootElement() ? staxbuild.getRootElement() : null; + + XMLStreamReader fragreader = inputfac.createXMLStreamReader(FidoFetch.getFido().getStream(resname)); + List contentlist = stxb.buildFragments(fragreader, new DefaultStAXFilter()); + Document fragbuild = new Document(); + fragbuild.addContent(contentlist); + Element fragroot = fragbuild.getRootElement(); + + SAXBuilder sb = new SAXBuilder(); + sb.setExpandEntities(expand); + + Document saxbuild = sb.build(FidoFetch.getFido().getURL(resname)); + Element saxroot = saxbuild.hasRootElement() ? saxbuild.getRootElement() : null; + + assertEquals("DOC SAX to StAXReader", toString(saxbuild), toString(staxbuild)); + assertEquals("ROOT SAX to StAXReader", toString(saxroot), toString(staxroot)); + assertEquals("DOC SAX to StAXReader FragmentList", toString(saxbuild), toString(fragbuild)); + assertEquals("ROOT SAX to StAXReader FragmentList", toString(saxroot), toString(fragroot)); + + } catch (Exception e) { + UnitTestUtil.failException("Could not parse file '" + resname + "': " + e.getMessage(), e); + } + } + + private void normalizeDTD(DocType dt) { + if (dt == null) { + return; + } + // do some tricks so that we can compare the results. + // these may well break the actual syntax of DTD's but for testing + // purposes it is OK. + String internalss = dt.getInternalSubset().trim() ; + // the spaceing in and around the internal subset is different between + // our SAX parse, and the DOM parse. + // make all whitespace a single space. + internalss = internalss.replaceAll("\\s+", " "); + // It seems the DOM parser internally quotes entities with single quote + // but our sax parser uses double-quote. + // simply replace all " with ' and be done with it. + internalss = internalss.replaceAll("\"", "'"); + dt.setInternalSubset("\n" + internalss + "\n"); + } + + private String toString(Document doc) { + UnitTestUtil.normalizeAttributes(doc.getRootElement()); + normalizeDTD(doc.getDocType()); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + CharArrayWriter caw = new CharArrayWriter(); + try { + out.output(doc, caw); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return caw.toString(); + } + + private String toString(Element emt) { + UnitTestUtil.normalizeAttributes(emt); + XMLOutputter2 out = new XMLOutputter2(Format.getPrettyFormat()); + CharArrayWriter caw = new CharArrayWriter(); + try { + out.output(emt, caw); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + return caw.toString(); + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderJAXPFactory.java b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderJAXPFactory.java new file mode 100644 index 0000000..b50b817 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderJAXPFactory.java @@ -0,0 +1,47 @@ +package org.jdom.test.cases.input.sax; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import javax.xml.parsers.FactoryConfigurationError; + +import org.junit.Test; + +import org.jdom.JDOMException; +import org.jdom.input.sax.XMLReaderJAXPFactory; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestXMLReaderJAXPFactory { + + //org.apache.xerces.jaxp.SAXParserFactoryImpl + + @Test + public void testJAXPXMLReaderFactoryDTDVal() throws JDOMException { + XMLReaderJAXPFactory readerfac = new XMLReaderJAXPFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", null, true); + assertTrue(readerfac.isValidating()); + assertNotNull(readerfac.createXMLReader()); + } + + @Test + public void testJAXPXMLReaderFactory() throws JDOMException { + XMLReaderJAXPFactory readerfac = new XMLReaderJAXPFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", null, false); + assertFalse(readerfac.isValidating()); + assertNotNull(readerfac.createXMLReader()); + } + + @Test + public void testSchemaXMLReaderFactoryNull() { + try { + assertTrue(null != new XMLReaderJAXPFactory( + null, null, false)); + UnitTestUtil.failNoException(FactoryConfigurationError.class); + } catch (Throwable e) { + UnitTestUtil.checkException(FactoryConfigurationError.class, e); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSAX2Factory.java b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSAX2Factory.java new file mode 100644 index 0000000..75fd8f7 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSAX2Factory.java @@ -0,0 +1,107 @@ +package org.jdom.test.cases.input.sax; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.junit.Test; +import org.xml.sax.SAXException; + +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaderSAX2Factory; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestXMLReaderSAX2Factory { + + @Test + public void testSAX2XMLReaderFactoryBoolean() throws JDOMException { + XMLReaderSAX2Factory facval = new XMLReaderSAX2Factory(true); + assertTrue(facval.isValidating()); + assertTrue(facval.createXMLReader() != null); + XMLReaderSAX2Factory facnon = new XMLReaderSAX2Factory(false); + assertFalse(facnon.isValidating()); + assertTrue(facnon.createXMLReader() != null); + } + + @Test + public void testSAX2XMLReaderFactoryBooleanString() throws JDOMException { + XMLReaderSAX2Factory facval = new XMLReaderSAX2Factory(true, null); + assertTrue(facval.isValidating()); + assertTrue(facval.createXMLReader() != null); + + facval = new XMLReaderSAX2Factory(true, + "com.sun.org.apache.xerces.internal.parsers.SAXParser"); + assertTrue(facval.isValidating()); + assertTrue(facval.createXMLReader() != null); + + XMLReaderSAX2Factory facnon = new XMLReaderSAX2Factory(false, null); + assertFalse(facnon.isValidating()); + assertTrue(facnon.createXMLReader() != null); + + facnon = new XMLReaderSAX2Factory(false, + "com.sun.org.apache.xerces.internal.parsers.SAXParser"); + assertFalse(facnon.isValidating()); + assertTrue(facnon.createXMLReader() != null); + } + + @Test + public void testGetDriverClassName() { + XMLReaderSAX2Factory facnon = new XMLReaderSAX2Factory(false, + "com.sun.org.apache.xerces.internal.parsers.SAXParser"); + assertFalse(facnon.isValidating()); + assertEquals("com.sun.org.apache.xerces.internal.parsers.SAXParser", + facnon.getDriverClassName()); + } + + @Test + public void testGetDummyDriver() { + XMLReaderSAX2Factory facnon = new XMLReaderSAX2Factory(false, + "does.not.exist"); + assertFalse(facnon.isValidating()); + try { + facnon.createXMLReader(); + UnitTestUtil.failNoException(JDOMException.class); + } catch (Exception e) { + UnitTestUtil.checkException(JDOMException.class, e); + UnitTestUtil.checkException(SAXException.class, e.getCause()); + } + } + + + @Test + public void testParseValidateWorks() throws JDOMException, IOException { + XMLReaderSAX2Factory fac = new XMLReaderSAX2Factory(true); + assertTrue(fac.isValidating()); + SAXBuilder builder = new SAXBuilder(fac); + Document doc = builder.build(FidoFetch.getFido().getURL("/DOMBuilder/doctype.xml")); + assertEquals("root", doc.getRootElement().getName()); + } + + @Test + public void testParseValidateFails() { + XMLReaderSAX2Factory fac = new XMLReaderSAX2Factory(true); + assertTrue(fac.isValidating()); + SAXBuilder builder = new SAXBuilder(fac); + try { + builder.build(FidoFetch.getFido().getURL("/DOMBuilder/attributes.xml")); + UnitTestUtil.failNoException(JDOMException.class); + } catch (Exception e) { + UnitTestUtil.checkException(JDOMException.class, e); + } + } + + @Test + public void testParseNonValidateWorks() throws JDOMException, IOException { + XMLReaderSAX2Factory fac = new XMLReaderSAX2Factory(false); + assertFalse(fac.isValidating()); + SAXBuilder builder = new SAXBuilder(fac); + Document doc = builder.build(FidoFetch.getFido().getURL("/DOMBuilder/attributes.xml")); + assertEquals("root", doc.getRootElement().getName()); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSchemaFactory.java b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSchemaFactory.java new file mode 100644 index 0000000..0a4a3cc --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSchemaFactory.java @@ -0,0 +1,93 @@ +package org.jdom.test.cases.input.sax; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import javax.xml.XMLConstants; +import javax.xml.parsers.FactoryConfigurationError; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.junit.Test; +import org.xml.sax.SAXException; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaderSchemaFactory; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestXMLReaderSchemaFactory { + + //org.apache.xerces.jaxp.SAXParserFactoryImpl + + @Test + public void testSchemaXMLReaderFactory() throws SAXException, JDOMException { + SchemaFactory schemafac = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = schemafac.newSchema(FidoFetch.getFido().getURL("/xsdcomplex/SAXTestComplexMain.xsd")); + XMLReaderSchemaFactory readerfac = new XMLReaderSchemaFactory(schema); + assertTrue(readerfac.isValidating()); + assertNotNull(readerfac.createXMLReader()); + } + + @Test + public void testSchemaXMLReaderFactoryXerces() throws SAXException, JDOMException { + SchemaFactory schemafac = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = schemafac.newSchema(FidoFetch.getFido().getURL("/xsdcomplex/SAXTestComplexMain.xsd")); + XMLReaderSchemaFactory readerfac = new XMLReaderSchemaFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", null, schema); + assertTrue(readerfac.isValidating()); + assertNotNull(readerfac.createXMLReader()); + } + + @Test + public void testSchemaXMLReaderFactoryNull() { + try { + new XMLReaderSchemaFactory(null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testSchemaXMLReaderFactoryNullFactory() { + try { + new XMLReaderSchemaFactory(null, null, null); + UnitTestUtil.failNoException(FactoryConfigurationError.class); + } catch (Throwable e) { + UnitTestUtil.checkException(FactoryConfigurationError.class, e); + } + } + + @Test + public void testParseValidateWorks() throws JDOMException, IOException, SAXException { + SchemaFactory schemafac = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = schemafac.newSchema(FidoFetch.getFido().getURL("/xsdcomplex/SAXTestComplexMain.xsd")); + XMLReaderSchemaFactory readerfac = new XMLReaderSchemaFactory(schema); + assertTrue(readerfac.isValidating()); + SAXBuilder builder = new SAXBuilder(readerfac); + Document doc = builder.build(FidoFetch.getFido().getURL("/xsdcomplex/input.xml")); + assertEquals("test", doc.getRootElement().getName()); + // the whole point of this particular XML input is that it should apply + // default attribute values.... lets make sure they make it. + int count = 4; + for (Element data : doc.getRootElement().getChildren("data", Namespace.getNamespace("http://www.jdom.org/tests/default"))) { + count--; + assertEquals("simple", data.getAttributeValue("type", Namespace.getNamespace("http://www.jdom.org/tests/imp"))); + } + assertTrue("" + count + " left", count == 0); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSingletons.java b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSingletons.java new file mode 100644 index 0000000..6d7dffa --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderSingletons.java @@ -0,0 +1,79 @@ +package org.jdom.test.cases.input.sax; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.junit.Test; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaders; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestXMLReaderSingletons { + + @Test + public void testNonValidatingReader() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING); + assertFalse(builder.isValidating()); + Document doc = builder.build(FidoFetch.getFido().getURL("/DOMBuilder/attributes.xml")); + assertEquals("root", doc.getRootElement().getName()); + } + + @Test + public void testDTDValidatingReader() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(XMLReaders.DTDVALIDATING); + assertTrue(builder.isValidating()); + Document doc = builder.build(FidoFetch.getFido().getURL("/DOMBuilder/doctype.xml")); + assertEquals("root", doc.getRootElement().getName()); + } + + @Test + public void testDTDValidatingReaderFails() { + SAXBuilder builder = new SAXBuilder(XMLReaders.DTDVALIDATING); + assertTrue(builder.isValidating()); + try { + builder.build(FidoFetch.getFido().getURL("/DOMBuilder/attributes.xml")); + UnitTestUtil.failNoException(JDOMException.class); + } catch (Exception e) { + UnitTestUtil.checkException(JDOMException.class, e); + } + } + + @Test + public void testXSDValidatingReader() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(XMLReaders.XSDVALIDATING); + assertTrue(builder.isValidating()); + Document doc = builder.build(FidoFetch.getFido().getURL("/xsdcomplex/input.xml")); + assertEquals("test", doc.getRootElement().getName()); + // the whole point of this particular XML input is that it should apply + // default attribute values.... lets make sure they make it. + int count = 4; + for (Element data : doc.getRootElement().getChildren("data", Namespace.getNamespace("http://www.jdom.org/tests/default"))) { + count--; + assertEquals("simple", data.getAttributeValue("type", Namespace.getNamespace("http://www.jdom.org/tests/imp"))); + } + assertTrue("" + count + " left", count == 0); + } + + @Test + public void testXSDValidatingReaderFails() { + SAXBuilder builder = new SAXBuilder(XMLReaders.XSDVALIDATING); + assertTrue(builder.isValidating()); + try { + builder.build(FidoFetch.getFido().getURL("/DOMBuilder/attributes.xml")); + UnitTestUtil.failNoException(JDOMException.class); + } catch (Exception e) { + UnitTestUtil.checkException(JDOMException.class, e); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderXSDFactory.java b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderXSDFactory.java new file mode 100644 index 0000000..a37d5a7 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/input/sax/TestXMLReaderXSDFactory.java @@ -0,0 +1,274 @@ +package org.jdom.test.cases.input.sax; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; + +import org.junit.Test; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; +import org.jdom.input.sax.XMLReaderJDOMFactory; +import org.jdom.input.sax.XMLReaderXSDFactory; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestXMLReaderXSDFactory { + //"./test/resources/xscomplex/multi_one.xsd", + + private final URL filemain() { + return FidoFetch.getFido().getURL("/xsdcomplex/multi_main.xsd"); + } + private final URL fileone() { + return FidoFetch.getFido().getURL("/xsdcomplex/multi_one.xsd"); + } + + private final URL filetwo() { + return FidoFetch.getFido().getURL("/xsdcomplex/multi_two.xsd"); + } + private final URL source() { + return FidoFetch.getFido().getURL("/xsdcomplex/multi.xml"); + } + + private void checkXML(XMLReaderJDOMFactory fac) { + SAXBuilder builder = new SAXBuilder(fac); + try { + Namespace nsmain = Namespace.getNamespace("http://www.jdom.org/schema_main"); + Namespace nsone = Namespace.getNamespace("http://www.jdom.org/schema_one"); + Namespace nstwo = Namespace.getNamespace("http://www.jdom.org/schema_two"); + + Document doc = builder.build(source()); + assertTrue(doc.hasRootElement()); + Element root = doc.getRootElement(); + assertTrue(nsmain == root.getNamespace()); + Element childone = root.getChild("child", nsone); + Element childtwo = root.getChild("child", nstwo); + assertTrue(childone != null); + assertTrue(childtwo != null); + + assertEquals("valueone", childone.getAttributeValue("attribute")); + assertEquals("valuetwo", childtwo.getAttributeValue("attribute")); + assertEquals("schema_one", childone.getAttributeValue("source")); + assertEquals("schema_two", childtwo.getAttributeValue("source")); + } catch (Exception e) { + UnitTestUtil.failException("Not expecting an exception", e); + } + + } + + @Test + public void testXMLReaderXSDFactoryStringArray() throws JDOMException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + filemain().toExternalForm(), + fileone().toExternalForm(), + filetwo().toExternalForm()); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactoryStringArrayJAXP() throws JDOMException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", (ClassLoader)null, + filemain().toExternalForm(), + fileone().toExternalForm(), + filetwo().toExternalForm()); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactoryURLArray() throws JDOMException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + filemain(), + fileone(), + filetwo()); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactoryURLArrayJAXP() throws JDOMException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", null, + filemain(), + fileone(), + filetwo()); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactoryFileArray() throws JDOMException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + filemain(), + fileone(), + filetwo()); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactoryFileArrayJAXP() throws JDOMException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", null, + filemain(), + fileone(), + filetwo()); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactorySourceArray() throws JDOMException, IOException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + new StreamSource(filemain().openStream()), + new StreamSource(fileone().openStream()), + new StreamSource(filetwo().openStream())); + checkXML(fac); + } + + @Test + public void testXMLReaderXSDFactorySourceArrayJJAXP() throws JDOMException, IOException { + XMLReaderJDOMFactory fac = new XMLReaderXSDFactory( + "org.apache.xerces.jaxp.SAXParserFactoryImpl", null, + new StreamSource(filemain().openStream()), + new StreamSource(fileone().openStream()), + new StreamSource(filetwo().openStream())); + checkXML(fac); + } + + /* Broken stuff */ + + @Test + public void testXMLReaderXSDFactoryStringNull() { + try { + String n = null; + new XMLReaderXSDFactory(n); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactoryURLNull() { + try { + URL n = null; + new XMLReaderXSDFactory(n); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactoryFileNull() { + try { + File n = null; + new XMLReaderXSDFactory(n); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactorySourceNull() { + try { + Source n = null; + new XMLReaderXSDFactory(n); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + + @Test + public void testXMLReaderXSDFactoryStringEmpty() { + try { + new XMLReaderXSDFactory(new String[0]); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactoryURLEmpty() { + try { + new XMLReaderXSDFactory(new URL[0]); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactoryFileEmpty() { + try { + new XMLReaderXSDFactory(new File[0]); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactorySourceEmpty() { + try { + new XMLReaderXSDFactory(new Source[0]); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + + + @Test + public void testXMLReaderXSDFactoryStringNullArray() { + try { + new XMLReaderXSDFactory((String[])null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactoryURLNullArray() { + try { + new XMLReaderXSDFactory((URL[])null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactoryFileNullArray() { + try { + new XMLReaderXSDFactory((File[])null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testXMLReaderXSDFactorySourceNullArray() { + try { + new XMLReaderXSDFactory((Source[])null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + +} diff --git a/test/src/java/org/jdom/test/cases/located/TestLocatedJDOMFactory.java b/test/src/java/org/jdom/test/cases/located/TestLocatedJDOMFactory.java new file mode 100644 index 0000000..a1d151f --- /dev/null +++ b/test/src/java/org/jdom/test/cases/located/TestLocatedJDOMFactory.java @@ -0,0 +1,74 @@ +package org.jdom.test.cases.located; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.IOException; + +import org.junit.Test; + +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.filter2.Filters; +import org.jdom.input.SAXBuilder; +import org.jdom.located.Located; +import org.jdom.located.LocatedJDOMFactory; +import org.jdom.test.cases.AbstractTestJDOMFactory; +import org.jdom.test.util.FidoFetch; +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings("javadoc") +public class TestLocatedJDOMFactory extends AbstractTestJDOMFactory { + + /** + * @param located + */ + public TestLocatedJDOMFactory() { + super(true); + } + + @Override + protected JDOMFactory buildFactory() { + return new LocatedJDOMFactory(); + } + + private final void checkLocation(Content c, int line, int col) { + assertTrue(c instanceof Located); + Located l = (Located)c; + if(line != l.getLine()) { + fail("Expected content " + l + " to be on line " + line + " but its on " + l.getLine()); + } + if(col != l.getColumn()) { + fail("Expected content " + l + " to be on col " + col + " but its on " + l.getColumn()); + } + } + + @Test + public void testLocation() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + sb.setJDOMFactory(new LocatedJDOMFactory()); + sb.setExpandEntities(false); + Document doc = sb.build(FidoFetch.getFido().getURL("/complex.xml")); + // it appears the location of the DocType is the start of the internal subset. + checkLocation(doc.getDocType(), 2, 16); + final Element root = doc.getRootElement(); + checkLocation(root, 3, 32); + + // content0 is first text.... + // tab counts as 1, not 4... thus char 2, not 5.... + checkLocation(root.getContent(0), 5, 2); + + // get the comment... + Comment comment = root.getContent(org.jdom.filter.Filters.comment()).get(0); + checkLocation(comment, 12, 19); + + Element leaf = XPathFactory.instance().compile("//leaf", Filters.element()).evaluateFirst(doc); + checkLocation(leaf, 21, 24); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/output/AbstractTestOutputter.java b/test/src/java/org/jdom/test/cases/output/AbstractTestOutputter.java new file mode 100644 index 0000000..adf3477 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/AbstractTestOutputter.java @@ -0,0 +1,1539 @@ +package org.jdom.test.cases.output; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.Parent; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; + +@SuppressWarnings("javadoc") +public abstract class AbstractTestOutputter { + + protected static interface FormatSetup { + public void setup(Format fmt); + } + + + private final boolean cr2xD; + private final boolean pademptyelement; + private final boolean forceexpand; + private final boolean padpi; + private final boolean usesrawxmlout; + + public AbstractTestOutputter(boolean cr2xD, boolean padpreempty, boolean padpi, + boolean forceexpand, boolean usesrawxmlout) { + this.cr2xD = cr2xD; + this.pademptyelement = padpreempty; + this.forceexpand = forceexpand; + this.padpi = padpi; + this.usesrawxmlout = usesrawxmlout; + } + + protected final String expect(String expect) { + if (cr2xD) { + expect = expect.replaceAll("\r", " "); + } + if (forceexpand) { + expect = expect.replaceAll("<(\\w+(:\\w+)?)\\s*/>", "<$1>"); + expect = expect.replaceAll("<(\\w+(:\\w+)?)\\s+(.+?)\"\\s*/>", "<$1 $3\">"); + } + if (padpi) { + expect = expect.replaceAll("(<\\?\\w+)(\\?>)", "$1 $2"); + } + if (pademptyelement) { + //expect = expect.replaceAll("\">", "\" >"); + //expect = expect.replaceAll("<(\\w+)>", "<$1 >"); + expect = expect.replaceAll("<(\\w+(:\\w+)?)/>", "<$1 />"); + expect = expect.replaceAll("<(\\w+(:\\w+)?\\s.+\")/>", "<$1 />"); + } else { + //expect = expect.replaceAll("<(\\w+)\\s+>", "<$1>"); + expect = expect.replaceAll("<(\\w+)(\\s+.+?)?\\s+/>", "<$1$2/>"); + expect = expect.replaceAll("<(\\w+:\\w+)(\\s+.+?)?\\s+/>", "<$1$2/>"); + } +// if (rawoutsideroot) { +// // outside the root element will be raw-formatted. +// StringBuilder sb = new StringBuilder(expect.length()); +// int gotstuff = 0; +// boolean indoctype = false; +// boolean gotroot = false; +// int depth = 0; +// char[] chars = expect.toCharArray(); +// int i = 0; +// while (i < chars.length && Verifier.isXMLWhitespace(chars[i])) { +// // skip initial whitespace. +// i++; +// } +// for (; i < chars.length; i++) { +// char c = chars[i]; +// sb.append(c); +// if (!gotroot) { +// if (c == '<') { +// if (depth == 0) { +// if (i < chars.length - 2) { +// if (chars[i + 1] == '?') { +// // PI or XML Declaration +// gotstuff++; +// } else if (chars[i + 1] == '!') { +// // Comment of DOCTYPE +// gotstuff++; +// if (chars[i + 2] == 'D') { +// // DOCTYPE +// indoctype = true; +// } +// } else { +// // root element +// gotroot = true; +// } +// } else { +// gotroot = true; +// } +// } +// depth++; +// } else if (c == '>') { +// depth--; +// if (depth == 0) { +// if (indoctype) { +// sb.append('\n'); +// indoctype = false; +// } +// while (i+1 < chars.length && Verifier.isXMLWhitespace(chars[i + 1])) { +// // skip whitespace after top-level content. +// i++; +// } +// } +// } +// } +// } +// while (Verifier.isXMLWhitespace(sb.charAt(sb.length() - 1))) { +// // eliminate trailing whitespace. +// sb.setLength(sb.length() - 1); +// } +// if (gotstuff > 1 || (gotroot && gotstuff > 0)) { +// // there is multiple content stuff, need to trim the whitespace.... +// expect = sb.toString(); +// } +// } + return expect; + } + + /** + * Return a string representing a {@link Document}. Uses an internal + * StringWriter. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param doc + * Document to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputDocumentAsString(Format format, Document doc); + + /** + * Return a string representing a {@link DocType}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param doctype + * DocType to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputDocTypeAsString(Format format, DocType doctype); + + /** + * Return a string representing an {@link Element}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param element + * Element to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputElementAsString(Format format, Element element); + + /** + * Return a string representing a List of {@link Content} nodes.
+ * The list is assumed to contain legal JDOM nodes. If other content is + * coerced on to the list it will cause ClassCastExceptions, and null List + * members will cause NullPointerException. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param list + * List to format. + * @return the input content formatted as an XML String. + * @throws ClassCastException + * if non-{@link Content} is forced in to the list + * @throws NullPointerException + * if the List is null or contains null members. + */ + public abstract String outputListAsString(Format format, List list); + + /** + * Return a string representing a {@link CDATA} node. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param cdata + * CDATA to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputCDataAsString(Format format, CDATA cdata); + + /** + * Return a string representing a {@link Text} node. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param text + * Text to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputTextAsString(Format format, Text text); + + /** + * Return a string representing a {@link Comment}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param comment + * Comment to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputCommentAsString(Format format, Comment comment); + + /** + * Return a string representing a {@link ProcessingInstruction}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param pi + * ProcessingInstruction to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputPIAsString(Format format, ProcessingInstruction pi); + + /** + * Return a string representing an {@link EntityRef}. + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param entity + * EntityRef to format. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputEntityRefAsString(Format format, EntityRef entity); + + /** + * This will handle printing out an {@link + * Element}'s content only, not including its tag, and attributes. + * This can be useful for printing the content of an element that contains + * HTML, like "<description>JDOM is + * <b>fun>!</description>". + *

+ * Warning: a String is Unicode, which may not match the outputter's + * specified encoding. + * + * @param element + * Element to output. + * @return the input content formatted as an XML String. + * @throws NullPointerException + * if the specified content is null. + */ + public abstract String outputElementContentString(Format format, Element element); + + protected static final Format fraw = Format.getRawFormat(); + protected static final Format frawfp = Format.getPrettyFormat().setTextMode(TextMode.PRESERVE); + protected static final Format fcompact = Format.getCompactFormat(); + protected static final Format fpretty = Format.getPrettyFormat(); + protected static final Format ftso = Format.getPrettyFormat(); + protected static final Format ftfw = Format.getPrettyFormat(); + + static { + fraw.setLineSeparator("\n"); + frawfp.setLineSeparator("\n"); + fcompact.setLineSeparator("\n"); + fpretty.setLineSeparator("\n"); + ftso.setLineSeparator("\n"); + ftso.setSpecifiedAttributesOnly(true); + ftfw.setLineSeparator("\n"); + ftfw.setTextMode(TextMode.TRIM_FULL_WHITE); + } + + + @Test + public void testTextEmpty() { + Text content = new Text(""); + assertEquals("", outputTextAsString(fraw, content)); + assertEquals("", outputTextAsString(frawfp, content)); + assertEquals("", outputTextAsString(fcompact, content)); + assertEquals("", outputTextAsString(fpretty, content)); + assertEquals("", outputTextAsString(ftso, content)); + assertEquals("", outputTextAsString(ftfw, content)); + } + + @Test + public void testTextWhitespace() { + Text content = new Text(" \r \n \t "); + assertEquals(expect(" \r \n \t "), + outputTextAsString(fraw, content)); + assertEquals(expect(" \r \n \t "), + outputTextAsString(frawfp, content)); + assertEquals("", + outputTextAsString(fcompact, content)); + assertEquals("", + outputTextAsString(fpretty, content)); + assertEquals("", + outputTextAsString(ftso, content)); + assertEquals("", + outputTextAsString(ftfw, content)); + } + + @Test + public void testTextWithText() { + Text content = new Text(" \r & \n \t "); + assertEquals(expect(" \r & \n \t "), + outputTextAsString(fraw, content)); + assertEquals(expect(" \r & \n \t "), + outputTextAsString(frawfp, content)); + assertEquals(expect("&"), + outputTextAsString(fcompact, content)); + assertEquals(expect("&"), + outputTextAsString(fpretty, content)); + assertEquals(expect("&"), + outputTextAsString(ftso, content)); + assertEquals(expect(" \r & \n \t "), + outputTextAsString(ftfw, content)); + } + + @Test + public void testCDATAEmpty() { + CDATA content = new CDATA(""); + assertEquals("", + outputCDataAsString(fraw, content)); + assertEquals("", + outputCDataAsString(frawfp, content)); + assertEquals("", + outputCDataAsString(fcompact, content)); + assertEquals("", + outputCDataAsString(fpretty, content)); + assertEquals("", + outputCDataAsString(ftso, content)); + assertEquals("", + outputCDataAsString(ftfw, content)); + } + + @Test + public void testCDATAWhitespace() { + CDATA content = new CDATA(" \r \n \t "); + assertEquals("", + outputCDataAsString(fraw, content)); + assertEquals("", + outputCDataAsString(frawfp, content)); + assertEquals("", + outputCDataAsString(fcompact, content)); + assertEquals("", + outputCDataAsString(fpretty, content)); + assertEquals("", + outputCDataAsString(ftso, content)); + assertEquals("", + outputCDataAsString(ftfw, content)); + } + + @Test + public void testCDATAWithText() { + CDATA content = new CDATA(" \r & \n \t "); + assertEquals("", + outputCDataAsString(fraw, content)); + assertEquals("", + outputCDataAsString(frawfp, content)); + assertEquals("", + outputCDataAsString(fcompact, content)); + assertEquals("", + outputCDataAsString(fpretty, content)); + assertEquals("", + outputCDataAsString(ftso, content)); + assertEquals("", + outputCDataAsString(ftfw, content)); + } + + @Test + public void testEntityRef() { + EntityRef content = new EntityRef("ref"); + assertEquals("&ref;", + outputEntityRefAsString(fraw, content)); + assertEquals("&ref;", + outputEntityRefAsString(frawfp, content)); + assertEquals("&ref;", + outputEntityRefAsString(fcompact, content)); + assertEquals("&ref;", + outputEntityRefAsString(fpretty, content)); + assertEquals("&ref;", + outputEntityRefAsString(ftso, content)); + assertEquals("&ref;", + outputEntityRefAsString(ftfw, content)); + } + + @Test + public void testProcessingInstructionTargetOnly() { + ProcessingInstruction content = new ProcessingInstruction("target"); + assertEquals(expect(""), + outputPIAsString(fraw, content)); + assertEquals(expect(""), + outputPIAsString(frawfp, content)); + assertEquals(expect(""), + outputPIAsString(fcompact, content)); + assertEquals(expect(""), + outputPIAsString(fpretty, content)); + assertEquals(expect(""), + outputPIAsString(ftfw, content)); + } + + @Test + public void testProcessingInstructionTargetWithData() { + ProcessingInstruction content = + new ProcessingInstruction("target", "data"); + assertEquals("", + outputPIAsString(fraw, content)); + assertEquals("", + outputPIAsString(frawfp, content)); + assertEquals("", + outputPIAsString(fcompact, content)); + assertEquals("", + outputPIAsString(fpretty, content)); + assertEquals("", + outputPIAsString(ftso, content)); + assertEquals("", + outputPIAsString(ftfw, content)); + } + + @Test + public void testComment() { + Comment content = new Comment("comment"); + assertEquals("", + outputCommentAsString(fraw, content)); + assertEquals("", + outputCommentAsString(frawfp, content)); + assertEquals("", + outputCommentAsString(fcompact, content)); + assertEquals("", + outputCommentAsString(fpretty, content)); + assertEquals("", + outputCommentAsString(ftso, content)); + assertEquals("", + outputCommentAsString(ftfw, content)); + } + + + @Test + public void testDocTypeSimple() { + DocType content = new DocType("root"); + assertEquals("", + outputDocTypeAsString(fraw, content)); + assertEquals("", + outputDocTypeAsString(frawfp, content)); + assertEquals("", + outputDocTypeAsString(fcompact, content)); + assertEquals("", + outputDocTypeAsString(fpretty, content)); + assertEquals("", + outputDocTypeAsString(ftso, content)); + assertEquals("", + outputDocTypeAsString(ftfw, content)); + } + + @Test + public void testDocTypeSimpleISS() { + DocType content = new DocType("root"); + content.setInternalSubset(""); + assertEquals("]>", + outputDocTypeAsString(fraw, content)); + assertEquals("]>", + outputDocTypeAsString(frawfp, content)); + assertEquals("]>", + outputDocTypeAsString(fcompact, content)); + assertEquals("]>", + outputDocTypeAsString(fpretty, content)); + assertEquals("]>", + outputDocTypeAsString(ftso, content)); + assertEquals("]>", + outputDocTypeAsString(ftfw, content)); + } + + @Test + public void testDocTypeSystemID() { + DocType content = new DocType("root", "sysid"); + assertEquals("", + outputDocTypeAsString(fraw, content)); + assertEquals("", + outputDocTypeAsString(frawfp, content)); + assertEquals("", + outputDocTypeAsString(fcompact, content)); + assertEquals("", + outputDocTypeAsString(fpretty, content)); + assertEquals("", + outputDocTypeAsString(ftso, content)); + assertEquals("", + outputDocTypeAsString(ftfw, content)); + } + + @Test + public void testDocTypeSystemIDISS() { + DocType content = new DocType("root", "sysid"); + content.setInternalSubset("internal"); + assertEquals("", + outputDocTypeAsString(fraw, content)); + assertEquals("", + outputDocTypeAsString(frawfp, content)); + assertEquals("", + outputDocTypeAsString(fcompact, content)); + assertEquals("", + outputDocTypeAsString(fpretty, content)); + assertEquals("", + outputDocTypeAsString(ftso, content)); + assertEquals("", + outputDocTypeAsString(ftfw, content)); + } + + @Test + public void testDocTypePublicSystemID() { + DocType content = new DocType("root", "pubid", "sysid"); + assertEquals("", + outputDocTypeAsString(fraw, content)); + assertEquals("", + outputDocTypeAsString(frawfp, content)); + assertEquals("", + outputDocTypeAsString(fcompact, content)); + assertEquals("", + outputDocTypeAsString(fpretty, content)); + assertEquals("", + outputDocTypeAsString(ftso, content)); + assertEquals("", + outputDocTypeAsString(ftfw, content)); + } + + @Test + public void testDocTypePublicSystemIDISS() { + DocType content = new DocType("root", "pubid", "sysid"); + content.setInternalSubset("internal"); + assertEquals("", + outputDocTypeAsString(fraw, content)); + assertEquals("", + outputDocTypeAsString(frawfp, content)); + assertEquals("", + outputDocTypeAsString(fcompact, content)); + assertEquals("", + outputDocTypeAsString(fpretty, content)); + assertEquals("", + outputDocTypeAsString(ftso, content)); + assertEquals("", + outputDocTypeAsString(ftfw, content)); + } + + @Test + public void testMultiWhiteText() { + Element root = new Element("root"); + root.addContent(new CDATA(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text("")); + root.addContent(new Text(" ")); + root.addContent(new Text(" \n \n ")); + root.addContent(new Text(" \t ")); + root.addContent(new Text(" ")); + assertEquals(expect(" \n \n \t "), + outputElementAsString(fraw, root)); + assertEquals(expect(" \n \n \t "), + outputElementAsString(frawfp, root)); + assertEquals(expect(""), + outputElementAsString(fcompact, root)); + assertEquals(expect(""), + outputElementAsString(fpretty, root)); + assertEquals(expect(""), + outputElementAsString(ftso, root)); + assertEquals(expect(""), + outputElementAsString(ftfw, root)); + } + + @Test + public void testMultiText() { + Element root = new Element("root"); + root.addContent(new CDATA(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text("")); + root.addContent(new Text("X")); + root.addContent(new Text(" \n \n ")); + root.addContent(new Text(" \t ")); + root.addContent(new Text(" ")); + assertEquals(expect(" X \n \n \t "), + outputElementAsString(fraw, root)); + assertEquals(expect(" X \n \n \t "), + outputElementAsString(frawfp, root)); + assertEquals(expect("X"), + outputElementAsString(fcompact, root)); + assertEquals(expect("X"), + outputElementAsString(fpretty, root)); + assertEquals(expect("X"), + outputElementAsString(ftso, root)); + assertEquals(expect(" X \n \n \t "), + outputElementAsString(ftfw, root)); + } + + @Test + public void testDocumentSimple() { + Document content = new Document(); + assertEquals("\n", + outputDocumentAsString(fraw, content)); + assertEquals("\n", + outputDocumentAsString(frawfp, content)); + assertEquals("\n", + outputDocumentAsString(fcompact, content)); + assertEquals("\n", + outputDocumentAsString(fpretty, content)); + assertEquals("\n", + outputDocumentAsString(ftso, content)); + assertEquals("\n", + outputDocumentAsString(ftfw, content)); + } + + @Test + public void testDocumentDocType() { + Document content = new Document(); + content.setDocType(new DocType("root")); + assertEquals("\n\n", + outputDocumentAsString(fraw, content)); + assertEquals("\n\n", + outputDocumentAsString(frawfp, content)); + assertEquals("\n\n", + outputDocumentAsString(fcompact, content)); + assertEquals("\n\n", + outputDocumentAsString(fpretty, content)); + assertEquals("\n\n", + outputDocumentAsString(ftso, content)); + assertEquals("\n\n", + outputDocumentAsString(ftfw, content)); + } + + @Test + public void testDocumentComment() { + Document content = new Document(); + content.addContent(new Comment("comment")); + assertEquals("\n\n", + outputDocumentAsString(fraw, content)); + assertEquals("\n\n", + outputDocumentAsString(frawfp, content)); + assertEquals("\n\n", + outputDocumentAsString(fcompact, content)); + assertEquals("\n\n", + outputDocumentAsString(fpretty, content)); + assertEquals("\n\n", + outputDocumentAsString(ftso, content)); + assertEquals("\n\n", + outputDocumentAsString(ftfw, content)); + } + + + + + + + + + + + @Test + public void testXXX() { + Text content = new Text(""); + assertEquals("", + outputTextAsString(fraw, content)); + assertEquals("", + outputTextAsString(frawfp, content)); + assertEquals("", + outputTextAsString(fcompact, content)); + assertEquals("", + outputTextAsString(fpretty, content)); + assertEquals("", + outputTextAsString(ftfw, content)); + } + + + + + + @Test + public void testOutputText() { + checkOutput(new Text(" hello there "), " hello there ", "hello there", "hello there", "hello there", " hello there "); + } + + @Test + public void testOutputCDATA() { + String indata = " hello there bozo ! "; + String rawcdata = ""; + String compdata = ""; + String prettydata = ""; + String trimdata = ""; + + checkOutput(new CDATA(indata), rawcdata, compdata, prettydata, prettydata, trimdata); + } + + @Test + public void testOutputComment() { + String incomment = " hello there bozo ! "; + String outcomment = ""; + checkOutput(new Comment(incomment), outcomment, outcomment, outcomment, outcomment, outcomment); + } + + @Test + public void testOutputProcessingInstructionSimple() { + ProcessingInstruction inpi = new ProcessingInstruction("jdomtest", ""); + String outpi = ""; + checkOutput(inpi, outpi, outpi, outpi, outpi, outpi); + } + + @Test + public void testOutputProcessingInstructionData() { + String pi = " hello there "; + ProcessingInstruction inpi = new ProcessingInstruction("jdomtest", pi); + String outpi = ""; + checkOutput(inpi, outpi, outpi, outpi, outpi, outpi); + } + + @Test + public void testOutputEntityRef() { + checkOutput(new EntityRef("name", "publicID", "systemID"), + "&name;", "&name;", "&name;", "&name;", "&name;"); + } + + @Test + public void testOutputElementSimple() { + String txt = ""; + checkOutput(new Element("root"), txt, txt, txt, txt, txt); + } + + @Test + public void testOutputElementAttribute() { + String txt = ""; + checkOutput(new Element("root").setAttribute("att", "val"), txt, txt, txt, txt, txt); + } + + @Test + public void testOutputElementAttributeNotSpecifiedA() { + String txt = ""; + final Element root = new Element("root"); + final Attribute att = new Attribute("att", "val"); + root.setAttribute(att); + att.setSpecified(false); + checkOutput(root, txt, txt, txt, "", txt); + } + + @Test + public void testOutputElementAttributeNotSpecifiedB() { + String txt = ""; + final Element root = new Element("root"); + final Attribute atta = new Attribute("atta", "val"); + final Attribute attb = new Attribute("attb", "attb"); + root.setAttribute(atta); + root.setAttribute(attb); + atta.setSpecified(false); + checkOutput(root, txt, txt, txt, "", txt); + } + + @Test + public void testOutputElementCDATA() { + String txt = ""; + Element root = new Element("root"); + root.addContent(new CDATA("xx")); + checkOutput(root, txt, txt, txt, txt, txt); + } + + @Test + public void testOutputElementExpandEmpty() { + String txt = ""; + FormatSetup setup = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(new Element("root"), setup, txt, txt, txt, txt, txt); + } + + @Test + public void testOutputElementPreserveSpace() { + String txt = " abc "; + Element root = new Element("root"); + root.setAttribute("space", "preserve", Namespace.XML_NAMESPACE); + root.addContent(" "); + Element child = new Element("child"); + child.setAttribute("space", "default", Namespace.XML_NAMESPACE); + child.addContent("abc"); + root.addContent(child); + root.addContent(" "); + checkOutput(root, txt, txt, txt, txt, txt); + } + + @Test + public void testOutputElementPreserveSpaceComplex() { + // the purpose of this test is to ensure that the different + // formatting values are used when going down one level, + // back up, then in to 'preserve', back up, and then again + // down in to normal (not preserve). + + // this is essentially a test of the FormatStack code.... + + Element tst = new Element("child"); + Comment cmt = new Comment("comment"); + tst.addContent(cmt); + String spaced = " \n \n \n"; + String compact = ""; + String preserved = ""; + Element root = new Element("root"); + root.addContent(tst.clone()); + root.addContent(tst.clone().setAttribute("space", "preserve", Namespace.XML_NAMESPACE)); + root.addContent(tst.clone()); + String rawcompact = "" + compact + preserved + compact + ""; + String pretty = "\n" + spaced + " " + preserved + "\n" + spaced + ""; + checkOutput(root, rawcompact, rawcompact, pretty, pretty, pretty); + } + + + @Test + public void testOutputElementMultiText() { + Element root = new Element("root"); + root.addContent(new CDATA(" ")); + root.addContent(new Text(" xx ")); + root.addContent(new Text("yy")); + root.addContent(new Text(" ")); + root.addContent(new Text("zz")); + root.addContent(new Text(" ww")); + root.addContent(new EntityRef("amp")); + root.addContent(new Text("vv")); + root.addContent(new Text(" ")); + checkOutput(root, + " xx yy zz ww&vv ", + "xx yy zz ww&vv", + "xx yy zz ww&vv", + "xx yy zz ww&vv", + // This should be changed with issue #31. + // The real value should have one additional + // space at the beginning and two at the end + // for now we leave the broken test here because it + // helps with the coverage reports. + // the next test is added to be a failing test. + " xx yy zz ww&vv "); + } + + @Test + public void testOutputElementMultiAllWhite() { + Element root = new Element("root"); + root.addContent(new CDATA(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text("")); + root.addContent(new Text(" ")); + root.addContent(new Text(" \n \n ")); + root.addContent(new Text(" \t ")); + root.addContent(new Text(" ")); + checkOutput(root, + " \n \n \t ", + "", + "", + "", + ""); + } + + @Test + public void testOutputElementMultiAllWhiteExpandEmpty() { + Element root = new Element("root"); + root.addContent(new CDATA(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text("")); + root.addContent(new Text(" ")); + root.addContent(new Text(" \n \n ")); + root.addContent(new Text(" \t ")); + root.addContent(new Text(" ")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " \n \n \t ", + "", + "", + "", + ""); + } + + @Test + public void testOutputElementMultiMostWhiteExpandEmpty() { + // this test has mixed content (text-type and not text type). + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new CDATA(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text(" ")); + root.addContent(new Text("")); + root.addContent(new Text(" ")); + root.addContent(new Text(" \n \n ")); + root.addContent(new Comment("Boo")); + root.addContent(new Text(" \t ")); + root.addContent(new Text(" ")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " \n \n \t ", + "", + "\n \n", + "\n \n", + "\n \n"); + } + + @Test + public void testOutputElementMixedMultiCDATA() { + // this test has mixed content (text-type and not text type). + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new Comment("Boo")); + root.addContent(new Text(" ")); + root.addContent(new CDATA("A")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " ", + "", + "\n \n \n", + "\n \n \n", + "\n \n \n"); + } + + @Test + public void testOutputElementMixedMultiEntityRef() { + // this test has mixed content (text-type and not text type). + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new Comment("Boo")); + root.addContent(new Text(" ")); + root.addContent(new EntityRef("aer")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " &aer;", + "&aer;", + "\n \n &aer;\n", + "\n \n &aer;\n", + "\n \n &aer;\n"); + } + + @Test + public void testOutputElementMixedMultiText() { + // this test has mixed content (text-type and not text type). + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new Comment("Boo")); + root.addContent(new Text(" ")); + root.addContent(new Text("txt")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " txt", + "txt", + "\n \n txt\n", + "\n \n txt\n", + "\n \n txt\n"); + } + + @Test + public void testOutputElementMixedMultiZeroText() { + // this test has mixed content (text-type and not text type). + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new Comment("Boo")); + root.addContent(new Text("")); + root.addContent(new Text(" ")); + root.addContent(new Text("")); + root.addContent(new Text("txt")); + root.addContent(new Text("")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " txt", + "txt", + "\n \n txt\n", + "\n \n txt\n", + "\n \n txt\n"); + } + + @Test + public void testOutputElementInterleavedEmptyText() { + // this is to test issue #72 + // Compact format only prints first child. + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new Text(" ")); + root.addContent(new Comment("Boo")); + root.addContent(new Text(" ")); + root.addContent(new Element("child")); + root.addContent(new Text(" ")); + root.addContent(new ProcessingInstruction("pitarget")); + root.addContent(new Text(" ")); + checkOutput(root, + " ", + "", + "\n \n \n \n", + "\n \n \n \n", + "\n \n \n \n"); + } + + @Test + public void testOutputElementMultiEntityLeftRight() { + Element root = new Element("root"); + root.addContent(new EntityRef("erl")); + root.addContent(new Text(" ")); + root.addContent(new Text(" ")); + root.addContent(new EntityRef("err")); + checkOutput(root, + "&erl; &err;", + "&erl; &err;", + "&erl; &err;", + "&erl; &err;", + "&erl; &err;"); + } + + @Test + public void testOutputElementMultiTrimLeftRight() { + Element root = new Element("root"); + root.addContent(new Text(" tl ")); + root.addContent(new Text(" mid ")); + root.addContent(new Text(" tr ")); + checkOutput(root, + " tl mid tr ", + "tl mid tr", + "tl mid tr", + "tl mid tr", + " tl mid tr "); + } + + @Test + public void testOutputElementMultiCDATALeftRight() { + Element root = new Element("root"); + root.addContent(new CDATA(" tl ")); + root.addContent(new Text(" mid ")); + root.addContent(new CDATA(" tr ")); + checkOutput(root, + " mid ", + " mid ", + " mid ", + " mid ", + " mid "); + } + + + + @Test + public void testOutputElementNamespaces() { + String txt = ""; + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + checkOutput(emt, + txt, txt,txt, txt, txt); + } + + @Test + public void testOutputDocTypeSimple() { + checkOutput(new DocType("root"), "", "", "", + "", ""); + } + + @Test + public void testOutputDocTypeInternalSubset() { + String dec = ""; + DocType dt = new DocType("root"); + dt.setInternalSubset("internal"); + checkOutput(dt, dec, dec, dec, dec, dec); + } + + @Test + public void testOutputDocTypeSystem() { + String dec = ""; + checkOutput(new DocType("root", "systemID"), dec, dec, dec, dec, dec); + } + + @Test + public void testOutputDocTypePublic() { + String dec = ""; + checkOutput(new DocType("root", "publicID", null), dec, dec, dec, dec, dec); + } + + @Test + public void testOutputDocTypePublicSystem() { + String dec = ""; + checkOutput(new DocType("root", "publicID", "systemID"), dec, dec, dec, dec, dec); + } + + @Test + public void testOutputDocumentSimple() { + Document doc = new Document(); + doc.addContent(new Element("root")); + String xmldec = ""; + String rtdec = ""; + checkOutput(doc, + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n"); + } + + @Test + public void testOutputDocumentOmitEncoding() { + Document doc = new Document(); + doc.addContent(new Element("root")); + String xmldec = ""; + FormatSetup setup = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setOmitEncoding(true); + } + }; + String rtdec = ""; + checkOutput(doc, setup, + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n", + xmldec + "\n" + rtdec + "\n"); + } + + @Test + public void testOutputDocumentOmitDeclaration() { + Document doc = new Document(); + doc.addContent(new Element("root")); + FormatSetup setup = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setOmitDeclaration(true); + } + }; + String rtdec = ""; + checkOutput(doc, setup, + rtdec + "\n", + rtdec + "\n", + rtdec + "\n", + rtdec + "\n", + rtdec + "\n"); + } + + @Test + public void testOutputDocumentFull() { + DocType dt = new DocType("root"); + Comment comment = new Comment("comment"); + ProcessingInstruction pi = new ProcessingInstruction("jdomtest", ""); + Element root = new Element("root"); + Document doc = new Document(); + doc.addContent(dt); + doc.addContent(comment); + doc.addContent(pi); + doc.addContent(root); + String xmldec = ""; + String dtdec = ""; + String commentdec = ""; + String pidec = ""; + String rtdec = ""; + String lf = "\n"; + String dlf = usesrawxmlout ? "" : lf; + checkOutput(doc, + xmldec + lf + dtdec + commentdec + pidec + rtdec + lf, + xmldec + lf + dtdec + commentdec + pidec + rtdec + lf, + xmldec + lf + dtdec + dlf + commentdec + dlf + pidec + dlf + rtdec + lf, + xmldec + lf + dtdec + dlf + commentdec + dlf + pidec + dlf + rtdec + lf, + xmldec + lf + dtdec + dlf + commentdec + dlf + pidec + dlf + rtdec + lf); + } + + @Test + public void testDeepNesting() { + // need to get beyond 16 levels of XML. + DocType dt = new DocType("root"); + Element root = new Element("root"); + Document doc = new Document(); + doc.addContent(dt); + doc.addContent(root); + String xmldec = ""; + String dtdec = ""; + String lf = "\n"; + + StringBuilder raw = new StringBuilder(); + raw.append(xmldec).append(lf).append(dtdec); + StringBuilder pretty = new StringBuilder(); + pretty.append(xmldec).append(lf).append(dtdec); + if (!usesrawxmlout) { + // most test systems use the XMLOutputter in raw mode to output + // the results of the conversion. In Raw mode the XMLOutputter will + // not make pretty content outside of the root element (but it will + // put the XMLDeclaration on it's own line). + // so, in the cases where the actual pretty format is used, we add + // this newline after the DocType... + pretty.append(lf); + } + raw.append(""); + pretty.append(""); + pretty.append(lf); + final int depth = 40; + int cnt = depth; + Parent parent = root; + StringBuilder indent = new StringBuilder(); + while (--cnt > 0) { + Element emt = new Element("emt"); + parent.getContent().add(emt); + parent = emt; + raw.append(""); + indent.append(" "); + pretty.append(indent.toString()); + pretty.append(""); + pretty.append(lf); + } + + parent.getContent().add(new Element("bottom")); + raw.append(""); + pretty.append(indent.toString()); + pretty.append(" "); + pretty.append(lf); + + cnt = depth; + while (--cnt > 0) { + raw.append(""); + pretty.append(indent.toString()); + pretty.append(""); + indent.setLength(indent.length() - 2); + pretty.append(lf); + } + raw.append(""); + raw.append(lf); + pretty.append(""); + pretty.append(lf); + + checkOutput(doc, raw.toString(), raw.toString(), pretty.toString(), pretty.toString(), pretty.toString()); + } + + @Test + public void testOutputElementContent() { + Element root = new Element("root"); + root.addContent(new Element("child")); + checkOutput(root, "outputElementContent", Element.class, null, "", "", "", "", ""); + } + + @Test + public void testOutputList() { + List c = new ArrayList(); + c.add(new Element("root")); + checkOutput(c, "output", List.class, null, "", "", "", "", ""); + } + + + @Test + public void testOutputEscapedMixedMultiText() { + // this test has mixed content (text-type and not text type). + // and, it has a multi-text-type at the end. + Element root = new Element("root"); + root.addContent(new Comment("Boo")); + root.addContent(new Text(" xx ")); + root.addContent(new Text("")); + root.addContent(new Text(" xx ")); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + checkOutput(root, fs, + " xx <emb> xx ", + "xx <emb> xx", + "\n \n xx <emb> xx\n", + "\n \n xx <emb> xx\n", + "\n \n xx <emb> xx \n"); + } + + @Test + public void testOutputLotsOfMixedMultiText() { + // this test has many text content members. + Element root = new Element("root"); + StringBuilder sb = new StringBuilder(); + sb.append("i"); + root.addContent("i"); + for (int i = 0; i < 100; i++) { + sb.append("&ent;"); + sb.append(i); + root.addContent(new EntityRef("ent")); + root.addContent("" + i); + } + sb.append(""); + FormatSetup fs = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setExpandEmptyElements(true); + } + }; + final String expect = sb.toString(); + checkOutput(root, fs, expect, expect, expect, expect, expect); + } + + + + protected void checkOutput(Object content, String raw, String compact, String pretty, String tso, String trimfw) { + Class clazz = content.getClass(); + checkOutput(content, "output", clazz, null, raw, compact, pretty, tso, trimfw); + } + + protected void checkOutput(Object content, FormatSetup setup, String raw, String compact, String pretty, String tso, String trimfw) { + Class clazz = content.getClass(); + checkOutput(content, "output", clazz, setup, raw, compact, pretty, tso, trimfw); + } + + private interface OutputRunner { + boolean matches(Class claxx); + String outputString(AbstractTestOutputter tester, Format format, Object data); + } + + private static abstract class AbstractRunner implements OutputRunner { + private final Class clazz; + + public AbstractRunner(Class claxx) { + this.clazz = claxx; + } + + @Override + public boolean matches(Class claxx) { + return this.clazz.isAssignableFrom(claxx); + } + + } + + OutputRunner ECRUNNER = new AbstractRunner(Element.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputElementContentString(format, (Element)data); + } + }; + + + private static final OutputRunner[] RUNNERS = { + + new AbstractRunner(Document.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputDocumentAsString(format, (Document)data); + } + }, + + new AbstractRunner(Element.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputElementAsString(format, (Element)data); + } + }, + + new AbstractRunner(EntityRef.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputEntityRefAsString(format, (EntityRef)data); + } + }, + new AbstractRunner(CDATA.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputCDataAsString(format, (CDATA)data); + } + }, + new AbstractRunner(Comment.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputCommentAsString(format, (Comment)data); + } + }, + new AbstractRunner(DocType.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputDocTypeAsString(format, (DocType)data); + } + }, + new AbstractRunner(ProcessingInstruction.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputPIAsString(format, (ProcessingInstruction)data); + } + }, + new AbstractRunner(Text.class) { + + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputTextAsString(format, (Text)data); + } + }, + new AbstractRunner(List.class) { + + @SuppressWarnings("unchecked") + @Override + public String outputString(AbstractTestOutputter tester, Format format, + Object data) { + return tester.outputListAsString(format, (List)data); + } + } + }; + + /** + * The following method will run the output data through each of the three base + * formatters, raw, compact, and pretty. It will also run each of those + * formatters as the outputString(content), output(content, OutputStream) + * and output(content, Writer). + * + * The expectation is that the results of the three output forms (String, + * OutputStream, and Writer) will be identical, and that it will match + * the expected value for the appropriate formatter. + * + * @param content The content to output + * @param methodprefix What the methods are called + * @param clazz The class used as the parameter for the methods. + * @param setup A callback mechanism to modify the formatters + * @param raw What we expect the content to look like with the RAW format + * @param compact What we expect the content to look like with the COMPACT format + * @param pretty What we expect the content to look like with the PRETTY format + * @param trimfw What we expect the content to look like with the TRIM_FULL_WHITE format + */ + protected void checkOutput(Object content, String methodprefix, Class clazz, + FormatSetup setup, String raw, String compact, String pretty, String tso, String trimfw) { + OutputRunner meth = getMyMethod(methodprefix, clazz); + + String[] descn = new String[] {"Raw", "PrettyPreserve", "Compact", "Pretty", "PrettySpecifiedOnly", "TrimFullWhite"}; + + Format ftrimfw = Format.getPrettyFormat(); + ftrimfw.setTextMode(TextMode.TRIM_FULL_WHITE); + Format fattspec = Format.getPrettyFormat(); + fattspec.setSpecifiedAttributesOnly(true); + Format[] formats = new Format[] { + getFormat(setup, Format.getRawFormat()), + getFormat(setup, Format.getPrettyFormat().setTextMode(TextMode.PRESERVE)), + getFormat(setup, Format.getCompactFormat()), + getFormat(setup, Format.getPrettyFormat()), + getFormat(setup, fattspec), + getFormat(setup, ftrimfw)}; + String[] result = new String[] {raw, raw, compact, pretty, tso, trimfw}; + + for (int i = 0; i < result.length; i++) { + + String mstring; + try { + mstring = meth.outputString(this, formats[i], content); + } catch (Exception e) { + e.printStackTrace(); + throw new IllegalStateException(e); + } + String msg = "outputString Format " + descn[i]; + assertEquals(msg, expect(result[i]), mstring); + } + } + + protected Format getFormat(FormatSetup setup, Format input) { + if (setup == null) { + input.setLineSeparator("\n"); + return input; + } + input.setLineSeparator("\n"); + setup.setup(input); + return input; + } + + private OutputRunner getMyMethod(String methpfx, Class claxx) { + if ("outputElementContent".equals(methpfx)) { + return ECRUNNER; + } + for (OutputRunner runner : RUNNERS) { + if (runner.matches(claxx)) { + return runner; + } + } + throw new IllegalStateException("Unable to find a runner for type " + claxx); + } + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestDOMOutputter.java b/test/src/java/org/jdom/test/cases/output/TestDOMOutputter.java new file mode 100644 index 0000000..3504817 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestDOMOutputter.java @@ -0,0 +1,650 @@ +package org.jdom.test.cases.output; + +/* Please run replic.pl on me ! */ +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.JUnitCore; +import org.w3c.dom.Attr; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.adapters.DOMAdapter; +import org.jdom.adapters.JAXPDOMAdapter; +import org.jdom.input.DOMBuilder; +import org.jdom.output.DOMOutputter; +import org.jdom.output.Format; +import org.jdom.output.LineSeparator; +import org.jdom.output.XMLOutputter2; +import org.jdom.output.support.AbstractDOMOutputProcessor; +import org.jdom.output.support.DOMOutputProcessor; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public final class TestDOMOutputter extends AbstractTestOutputter { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestDOMOutputter.class); + } + + private interface DOMSetup { + public DOMOutputter buildOutputter(); + } + + public TestDOMOutputter() { + super(true, true, false, false, true); + } + + @SuppressWarnings("deprecation") + @Test + public void test_ForceNamespaces() throws JDOMException { + Document doc = new Document(); + Element root = new Element("root"); + Element el = new Element("a"); + el.setText("abc"); + root.addContent(el); + doc.setRootElement(root); + + DOMOutputter out = new DOMOutputter(); + // deprecated stuff.. + assertTrue(out.getForceNamespaceAware()); + out.setForceNamespaceAware(false); + assertTrue(out.getForceNamespaceAware()); + org.w3c.dom.Document dom = out.output(doc); + org.w3c.dom.Element domel = dom.getDocumentElement(); + //System.out.println("Dom impl: "+ domel.getClass().getName()); + assertNotNull(domel.getLocalName()); + } + + private void roundTrip(Document doc) { + roundTrip(null, doc); + } + + private void roundTrip(DOMSetup setup, Document doc) { + XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + // create a String representation of the input. + if (doc.hasRootElement()) { + UnitTestUtil.normalizeAttributes(doc.getRootElement()); + } + String expect = xout.outputString(doc); + + String actual = null; + try { + // convert the input to a DOM document + DOMOutputter domout = setup == null ? new DOMOutputter() : setup.buildOutputter(); + org.w3c.dom.Document outonce = domout.output(doc); + + // convert the DOM document back again. + DOMBuilder builder = new DOMBuilder(); + Document backagain = builder.build(outonce); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + UnitTestUtil.normalizeAttributes(backagain.getRootElement()); + } + actual = xout.outputString(backagain); + } catch (JDOMException e) { + e.printStackTrace(); + fail("Failed to round-trip the document with exception: " + + e.getMessage() + "\n" + expect); + } + assertEquals(expect, actual); + } + + @Test + public void testDOMAdapter() { + Document doc = new Document(new Element("root")); + DOMSetup setup = new DOMSetup() { + @Override + public DOMOutputter buildOutputter() { + return new DOMOutputter(); + } + }; + roundTrip(setup, doc); + } + + @Test + public void testDOMOutputDocumentSimple() { + Document doc = new Document(); + doc.addContent(new Element("root")); + roundTrip(doc); + } + + @Test + public void testDOMOutputDocumentFull() { + Document doc = new Document(); + doc.addContent(new DocType("root")); + doc.addContent(new Comment("This is a document")); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + doc.addContent(new Element("root")); + roundTrip(doc); + } + + @Test + public void testDOMOutputElementAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + Document doc = new Document(emt); + roundTrip(doc); + } + + @Test + public void testDOMOutputElementNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + Document doc = new Document(emt); + roundTrip(doc); + } + + @Test + public void testOutputElementNamespaceNoPrefix() { + Element emt = new Element("root", Namespace.getNamespace("", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + Document doc = new Document(emt); + roundTrip(doc); + } + + @Test + public void testOutputElementNamespacesForceNS() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + Document doc = new Document(emt); + DOMSetup setup = new DOMSetup() { + @SuppressWarnings("deprecation") + @Override + public DOMOutputter buildOutputter() { + DOMOutputter dom = new DOMOutputter(); + dom.setForceNamespaceAware(true); + return dom; + } + }; + roundTrip(setup, doc); + } + + @Test + public void testOutputElementFull() { + Element emt = new Element("root"); + emt.setAttribute("att1", "val1"); + emt.setAttribute("att2", "val2"); + emt.addContent(new Comment("comment")); + emt.addContent(new Text("txt")); + emt.addContent(new ProcessingInstruction("jdomtest", "")); + emt.addContent(new Element("child")); + emt.addContent(new EntityRef("ref")); + emt.addContent(new CDATA("cdata")); + Document doc = new Document(emt); + roundTrip(doc); + } + + @Test + public void testOutputElementFullForceNS() { + Element emt = new Element("root"); + emt.setAttribute("att1", "val1"); + emt.setAttribute("att2", "val2"); + emt.addContent(new Comment("comment")); + emt.addContent(new Text("txt")); + emt.addContent(new ProcessingInstruction("jdomtest", "")); + emt.addContent(new Element("child")); + emt.addContent(new EntityRef("ref")); + emt.addContent(new CDATA("cdata")); + Document doc = new Document(emt); + DOMSetup setup = new DOMSetup() { + @SuppressWarnings("deprecation") + @Override + public DOMOutputter buildOutputter() { + DOMOutputter dom = new DOMOutputter(); + dom.setForceNamespaceAware(true); + return dom; + } + }; + roundTrip(setup, doc); + } + + @Test + public void testWithDocType() { + DocType dt = new DocType("root"); + dt.setInternalSubset(""); + Element root = new Element("root"); + Document doc = new Document(root, dt); + + roundTrip(doc); + } + + + @Test + public void testGetSetFormat() { + DOMOutputter dout = new DOMOutputter(); + Format def = dout.getFormat(); + assertTrue(def != null); + Format f = Format.getPrettyFormat(); + dout.setFormat(f); + assertTrue(f == dout.getFormat()); + dout.setFormat(null); + TestFormat.checkEquals(def, dout.getFormat()); + } + + @Test + public void testGetSetDOMOutputProcessor() { + DOMOutputProcessor dop = new AbstractDOMOutputProcessor() { + // nothing. + }; + + DOMOutputter dout = new DOMOutputter(); + DOMOutputProcessor def = dout.getDOMOutputProcessor(); + assertTrue(def != null); + dout.setDOMOutputProcessor(dop); + assertTrue(dop == dout.getDOMOutputProcessor()); + dout.setDOMOutputProcessor(null); + assertEquals(def, dout.getDOMOutputProcessor()); + } + + @Test + public void testGetSetDOMAdapter() { + DOMAdapter dop = new JAXPDOMAdapter(); + + DOMOutputter dout = new DOMOutputter(); + DOMAdapter def = dout.getDOMAdapter(); + assertTrue(def != null); + dout.setDOMAdapter(dop); + assertTrue(dop == dout.getDOMAdapter()); + dout.setDOMAdapter(null); + assertEquals(def, dout.getDOMAdapter()); + } + + @Test + public void testFullConstructor() { + DOMOutputProcessor dop = new AbstractDOMOutputProcessor() { + // nothing. + }; + + DOMAdapter dap = new JAXPDOMAdapter(); + + Format fmt = Format.getPrettyFormat(); + + DOMOutputter out = new DOMOutputter(null, null, null); + + DOMOutputProcessor defop = out.getDOMOutputProcessor(); + DOMAdapter defap = out.getDOMAdapter(); + Format defmt = out.getFormat(); + + out = new DOMOutputter(dap, fmt, dop); + + assertTrue(dap == out.getDOMAdapter()); + assertTrue(dop == out.getDOMOutputProcessor()); + assertTrue(fmt == out.getFormat()); + + out = new DOMOutputter(null, null, null); + assertTrue(defap == out.getDOMAdapter()); + assertTrue(defop == out.getDOMOutputProcessor()); + TestFormat.checkEquals(defmt , out.getFormat()); + } + + @Test + public void testDOMOutputProcessorConstructor() { + DOMOutputProcessor dop = new AbstractDOMOutputProcessor() { + // nothing to add + }; + + DOMOutputter out = new DOMOutputter((DOMOutputProcessor)null); + + DOMOutputProcessor defap = out.getDOMOutputProcessor(); + + out = new DOMOutputter(dop); + + assertTrue(dop == out.getDOMOutputProcessor()); + + out = new DOMOutputter((DOMOutputProcessor)null); + assertTrue(defap == out.getDOMOutputProcessor()); + } + + @Test + public void testDOMAdapterConstructor() { + DOMAdapter dap = new JAXPDOMAdapter(); + + DOMOutputter out = new DOMOutputter((DOMAdapter)null); + + DOMAdapter defap = out.getDOMAdapter(); + + out = new DOMOutputter(dap); + + assertTrue(dap == out.getDOMAdapter()); + + out = new DOMOutputter((DOMAdapter)null); + assertTrue(defap == out.getDOMAdapter()); + } + + @Test + @SuppressWarnings("deprecation") + public void testDOMAdapterNameConstructor() { + DOMOutputter out = new DOMOutputter((String)null); + assertTrue(out.getDOMAdapter() instanceof JAXPDOMAdapter); + out = new DOMOutputter(JAXPDOMAdapter.class.getName()); + assertTrue(out.getDOMAdapter() instanceof JAXPDOMAdapter); + } + + @Test + public void testOutputAttribute() throws JDOMException { + DOMOutputter out = getOutputter(Format.getRawFormat()); + org.w3c.dom.Document domdoc = out.getDOMAdapter().createDocument(); + Attribute att = new Attribute("name", "val"); + + Attr doma = out.output(att); + assertEquals("name", doma.getNodeName()); + assertEquals("name", doma.getLocalName()); + assertEquals("val", doma.getNodeValue()); + // Android can have "" values, xerces has null. Technically it is implementation dependant + assertTrue(null == doma.getPrefix() || "".equals(doma.getPrefix())); + // Android can have "" values, xerces has null. Technically it is implementation dependant + assertTrue(null == doma.getNamespaceURI() || "".equals(doma.getNamespaceURI())); + assertTrue(domdoc != doma.getOwnerDocument()); + + doma = out.output(domdoc, att); + assertEquals("name", doma.getNodeName()); + assertEquals("name", doma.getLocalName()); + assertEquals("val", doma.getNodeValue()); + assertTrue(null == doma.getPrefix() || "".equals(doma.getPrefix())); + assertTrue(null == doma.getNamespaceURI() || "".equals(doma.getNamespaceURI())); + assertTrue(domdoc == doma.getOwnerDocument()); + + att = new Attribute("name", "val", Namespace.getNamespace("ns", "http://jdom.org/junit/ns")); + + doma = out.output(att); + assertEquals("ns:name", doma.getNodeName()); + assertEquals("name", doma.getLocalName()); + assertEquals("val", doma.getNodeValue()); + assertEquals("ns", doma.getPrefix()); + assertEquals("http://jdom.org/junit/ns", doma.getNamespaceURI()); + assertTrue(domdoc != doma.getOwnerDocument()); + + doma = out.output(domdoc, att); + assertEquals("ns:name", doma.getNodeName()); + assertEquals("name", doma.getLocalName()); + assertEquals("val", doma.getNodeValue()); + assertEquals("ns", doma.getPrefix()); + assertEquals("http://jdom.org/junit/ns", doma.getNamespaceURI()); + assertTrue(domdoc == doma.getOwnerDocument()); + + } + + private final DOMOutputter getOutputter(Format format) { + DOMOutputter outputter = new DOMOutputter(); + outputter.setFormat(format); + return outputter; + } + + private final String nodeToString (Object input) { + try { + // Do not use Transforms, messes up end-of-lines, etc. + // instead, reuse the DOMBuilder and convert back to string using + // XMLOutputter. + if (input == null) { + return ""; + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + DOMBuilder builder = new DOMBuilder(); + + if (input instanceof List) { + StringBuilder sb = new StringBuilder(); + for (Object o : (List)input) { + sb.append(nodeToString(o)); + } + return sb.toString(); + } + if (input instanceof org.w3c.dom.Document) { + return xout.outputString(builder.build((org.w3c.dom.Document)input)); + } + if (input instanceof org.w3c.dom.CDATASection) { + return xout.outputString(builder.build((org.w3c.dom.CDATASection)input)); + } + if (input instanceof org.w3c.dom.Comment) { + return xout.outputString(builder.build((org.w3c.dom.Comment)input)); + } + if (input instanceof org.w3c.dom.DocumentType) { + return xout.outputString(builder.build((org.w3c.dom.DocumentType)input)); + } + if (input instanceof org.w3c.dom.Element) { + return xout.outputString(builder.build((org.w3c.dom.Element)input)); + } + if (input instanceof org.w3c.dom.EntityReference) { + return xout.outputString(builder.build((org.w3c.dom.EntityReference)input)); + } + if (input instanceof org.w3c.dom.ProcessingInstruction) { + return xout.outputString(builder.build((org.w3c.dom.ProcessingInstruction)input)); + } + if (input instanceof org.w3c.dom.Text) { + return xout.outputString(builder.build((org.w3c.dom.Text)input)); + } + + return null; +// TransformerFactory factory = TransformerFactory.newInstance(); +// Transformer transformer = factory.newTransformer(); +// transformer.setOutputProperty("{http://xml.apache.org/xalan}line-separator","\n"); +// StringWriter writer = new StringWriter(); +// for (Node node : nodes) { +// if (node instanceof org.w3c.dom.Document) { +// ((org.w3c.dom.Document)node).setXmlStandalone(true); +// transformer.setOutputProperty("omit-xml-declaration", "no"); +// } else { +// transformer.setOutputProperty("omit-xml-declaration", "yes"); +// } +// Result result = new StreamResult(writer); +// Source source = new DOMSource(node); +// transformer.transform(source, result); +// } +// writer.close(); +// String xml = writer.toString(); +// return xml; + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + @Override + public String outputDocumentAsString(Format format, Document doc) { + try { + DOMOutputter out = getOutputter(format); + String opta = nodeToString(out.output(doc)); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputDocTypeAsString(Format format, DocType doctype) { + try { + DOMOutputter out = getOutputter(format); +// org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(doctype)); +// String optb = nodeToString(out.output(doc, doctype)); +// assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputElementAsString(Format format, Element element) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(element)); + String optb = nodeToString(out.output(doc, element)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputListAsString(Format format, List list) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(list)); + String optb = nodeToString(out.output(doc, list)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputCDataAsString(Format format, CDATA cdata) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(cdata)); + String optb = nodeToString(out.output(doc, cdata)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputTextAsString(Format format, Text text) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(text)); + String optb = nodeToString(out.output(doc, text)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputCommentAsString(Format format, Comment comment) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(comment)); + String optb = nodeToString(out.output(doc, comment)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputPIAsString(Format format, ProcessingInstruction pi) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(pi)); + String optb = nodeToString(out.output(doc, pi)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputEntityRefAsString(Format format, EntityRef entity) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(entity)); + String optb = nodeToString(out.output(doc, entity)); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + public String outputElementContentString(Format format, Element element) { + try { + DOMOutputter out = getOutputter(format); + org.w3c.dom.Document doc = out.getDOMAdapter().createDocument(); + String opta = nodeToString(out.output(element.getContent())); + String optb = nodeToString(out.output(doc, element.getContent())); + assertEquals(opta, optb); + return opta; + } catch (JDOMException e) { + UnitTestUtil.failException("Unexpected JDOMException", e); + return null; + } + } + + @Override + @Ignore // we can't control expand in DOM. + public void testOutputElementExpandEmpty() { + // pass this test always. + } + + @Override + @Ignore // we can't control expand in DOM. + public void testOutputElementMultiAllWhiteExpandEmpty() { + // nothing. + } + + @Override + @Ignore // we can't control expand in DOM. + public void testOutputDocumentOmitEncoding() { + // nothing. + } + + @Override + @Ignore // we can't control expand in DOM. + public void testOutputDocumentOmitDeclaration() { + // nothing. + } + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestDOMOutputterXmlnsNamespaces.java b/test/src/java/org/jdom/test/cases/output/TestDOMOutputterXmlnsNamespaces.java new file mode 100644 index 0000000..7e30eca --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestDOMOutputterXmlnsNamespaces.java @@ -0,0 +1,147 @@ +package org.jdom.test.cases.output; + +/* Please run replic.pl on me ! */ +/** + * Tests that the namespace of xmlns attributes from DOMOutputter are correct + * + * @author unascribed + * @version 0.1 + */ + +import org.jdom.Element; +import org.jdom.JDOMConstants; +import org.jdom.input.DOMBuilder; +import org.jdom.input.SAXBuilder; +import org.jdom.output.DOMOutputter; + +import org.w3c.dom.Document; +import org.xml.sax.InputSource; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.StringReader; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import org.junit.runner.JUnitCore; + +@SuppressWarnings("javadoc") +public class TestDOMOutputterXmlnsNamespaces +{ + /** + * The XML document to test - this document + */ + private static final String XML = ""; + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestDOMOutputterXmlnsNamespaces.class); + } + + + /** + * Parse the XML using DOM + * + * @throws Exception + */ + @Test + public void testDocumentBuilderSource() throws Exception + { + org.w3c.dom.Element element = parseDOM(XML); + + final String namespace = element.getAttributeNode("xmlns:test").getNamespaceURI(); + + assertEquals("DocumentBuilder output", JDOMConstants.NS_URI_XMLNS, namespace); + } + + + /** + * Parse the XML using JDOM, convert to DOM + * + * @throws Exception + */ + @Test + public void testJdomToDom() throws Exception + { + org.w3c.dom.Element element = jdomToDom(parseJDOM(XML)); // load JDOM and convert to DOM + + final String namespace = element.getAttributeNode("xmlns:test").getNamespaceURI(); + + assertEquals("SAXBuilder->DOMOutputter output", JDOMConstants.NS_URI_XMLNS, namespace); + } + + + /** + * Parse the XML using DOM, then convert to JDOM and then finally back to DOM + * + * @throws Exception + */ + + @Test + public void testDomToJdomToDom() throws Exception + { + org.w3c.dom.Element element = jdomToDom(domToJdom(parseDOM(XML))); // load DOM, convert to JDOM and back to DOM + + final String namespace = element.getAttributeNode("xmlns:test").getNamespaceURI(); + + assertEquals("DocumentBuilder->DOMBuilder->DOMOutputter output", JDOMConstants.NS_URI_XMLNS, namespace); + } + + + private Element parseJDOM(String xml) throws Exception + { + final StringReader src = new StringReader(xml); + + return new SAXBuilder().build(src).getRootElement(); + } + + + private org.w3c.dom.Element parseDOM(String xml) throws Exception + { + InputSource src = new InputSource(new StringReader(xml)); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + + DocumentBuilder documentBuilder = factory.newDocumentBuilder(); + + Document result = documentBuilder.parse(src); + + return result.getDocumentElement(); + } + + + /** + * Converts a JDOM Element to a DOM Element + * + * @param element + * + * @return + * + * @throws Exception + */ + private org.w3c.dom.Element jdomToDom(org.jdom.Element element) throws Exception + { + return new DOMOutputter().output(element); + } + + + /** + * Converts a DOM Element to a JDOM Element + * + * @param element + * + * @return + * + * @throws Exception + */ + private org.jdom.Element domToJdom(org.w3c.dom.Element element) throws Exception + { + final DOMBuilder builder = new DOMBuilder(); + + return builder.build(element); + } +} \ No newline at end of file diff --git a/test/src/java/org/jdom/test/cases/output/TestFormat.java b/test/src/java/org/jdom/test/cases/output/TestFormat.java new file mode 100644 index 0000000..8b52c23 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestFormat.java @@ -0,0 +1,268 @@ +package org.jdom.test.cases.output; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.jdom.output.EscapeStrategy; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestFormat { + + public static final void checkEquals(Format a, Format b) { + assertEquals("Expect formatters to have the same Encoding", + a.getEncoding(), b.getEncoding()); + //assertEquals("Expect formatters to have the same EscapeStrategy", + // a.getEscapeStrategy(), b.getEscapeStrategy()); + assertEquals("Expect formatters to have the same ExpandEmptyElements", + a.getExpandEmptyElements(), b.getExpandEmptyElements()); + assertEquals("Expect formatters to have the same TrAXEscapingPIs", + a.getIgnoreTrAXEscapingPIs(), b.getIgnoreTrAXEscapingPIs()); + assertEquals("Expect formatters to have the same Indent", + a.getIndent(), b.getIndent()); + assertEquals("Expect formatters to have the same LineSeparator", + a.getLineSeparator(), b.getLineSeparator()); + assertEquals("Expect formatters to have the same OmitDeclaration", + a.getOmitDeclaration(), b.getOmitDeclaration()); + assertEquals("Expect formatters to have the same OmitEncoding", + a.getOmitEncoding(), b.getOmitEncoding()); + assertEquals("Expect formatters to have the same TextMode", + a.getTextMode(), b.getTextMode()); + + } + + + + @Test + public void testGetRawFormat() { + Format mine = Format.getRawFormat(); + assertTrue(mine.getTextMode() == TextMode.PRESERVE); + assertEquals(mine.getEncoding(), "UTF-8"); + } + + @Test + public void testGetPrettyFormat() { + Format mine = Format.getPrettyFormat(); + assertTrue(mine.getTextMode() == TextMode.TRIM); + assertEquals(mine.getEncoding(), "UTF-8"); + } + + @Test + public void testGetCompactFormat() { + Format mine = Format.getCompactFormat(); + assertTrue(mine.getTextMode() == TextMode.NORMALIZE); + assertEquals(mine.getEncoding(), "UTF-8"); + } + + @Test + public void testEscapeStrategy() { + EscapeStrategy es = new EscapeStrategy() { + @Override + public boolean shouldEscape(char ch) { + return false; + } + }; + Format mine = Format.getRawFormat(); + assertTrue(mine.getEscapeStrategy() != es); + mine.setEscapeStrategy(es); + assertTrue(mine.getEscapeStrategy() == es); + } + + @Test + public void testLineSeparator() { + assertEquals("\r\n", Format.getPrettyFormat().getLineSeparator()); + assertEquals("\r\n", Format.getCompactFormat().getLineSeparator()); + + Format mine = Format.getRawFormat(); + assertEquals("\r\n", mine.getLineSeparator()); + mine.setLineSeparator(" \n "); + assertEquals(" \n ", mine.getLineSeparator()); + } + + @Test + public void testOmitEncoding() { + assertFalse(Format.getPrettyFormat().getOmitEncoding()); + assertFalse(Format.getCompactFormat().getOmitEncoding()); + Format mine = Format.getRawFormat(); + assertFalse(mine.getOmitEncoding()); + mine.setOmitEncoding(true); + assertTrue (mine.getOmitEncoding()); + } + + @Test + public void testOmitDeclaration() { + assertFalse(Format.getPrettyFormat().getOmitDeclaration()); + assertFalse(Format.getCompactFormat().getOmitDeclaration()); + Format mine = Format.getRawFormat(); + assertFalse(mine.getOmitDeclaration()); + mine.setOmitDeclaration(true); + assertTrue (mine.getOmitDeclaration()); + } + + @Test + public void testSpecifiedAttributesOnly() { + assertFalse(Format.getPrettyFormat().isSpecifiedAttributesOnly()); + assertFalse(Format.getCompactFormat().isSpecifiedAttributesOnly()); + Format mine = Format.getRawFormat(); + assertFalse(mine.isSpecifiedAttributesOnly()); + mine.setSpecifiedAttributesOnly(true); + assertTrue (mine.isSpecifiedAttributesOnly()); + } + + @Test + public void testExpandEmptyElements() { + assertFalse(Format.getPrettyFormat().getExpandEmptyElements()); + assertFalse(Format.getCompactFormat().getExpandEmptyElements()); + Format mine = Format.getRawFormat(); + assertFalse(mine.getExpandEmptyElements()); + mine.setExpandEmptyElements(true); + assertTrue (mine.getExpandEmptyElements()); + } + + @Test + public void testIgnoreTrAXEscapingPIs() { + assertFalse(Format.getPrettyFormat().getIgnoreTrAXEscapingPIs()); + assertFalse(Format.getCompactFormat().getIgnoreTrAXEscapingPIs()); + Format mine = Format.getRawFormat(); + assertFalse(mine.getIgnoreTrAXEscapingPIs()); + mine.setIgnoreTrAXEscapingPIs(true); + assertTrue (mine.getIgnoreTrAXEscapingPIs()); + } + + @Test + public void testTextMode() { + assertEquals(TextMode.TRIM, Format.getPrettyFormat().getTextMode()); + assertEquals(TextMode.NORMALIZE, Format.getCompactFormat().getTextMode()); + + Format mine = Format.getRawFormat(); + assertEquals(TextMode.PRESERVE, mine.getTextMode()); + mine.setTextMode(TextMode.TRIM_FULL_WHITE); + assertEquals(TextMode.TRIM_FULL_WHITE, mine.getTextMode()); + + assertEquals("TRIM", TextMode.TRIM.toString()); + } + + @Test + public void testIndent() { + assertEquals(" ", Format.getPrettyFormat().getIndent()); + assertEquals(null, Format.getCompactFormat().getIndent()); + + Format mine = Format.getRawFormat(); + assertEquals(null, mine.getIndent()); + mine.setIndent(" "); + assertEquals(" ", mine.getIndent()); + } + + @Test + public void testEncoding() { + assertEquals("UTF-8", Format.getPrettyFormat().getEncoding()); + assertEquals("UTF-8", Format.getCompactFormat().getEncoding()); + + Format mine = Format.getRawFormat(); + assertEquals("UTF-8", mine.getEncoding()); + mine.setEncoding("US-ASCII"); + assertEquals("US-ASCII", mine.getEncoding()); + } + + @Test + public void testClone() { + Format mine = Format.getRawFormat(); + Format clone = mine.clone(); + assertFalse(mine == clone); + checkEquals(mine, clone); + } + + @Test + public void test7BitEscapes() { + checkBitEscape("US-ASCII", + new char[] {'a', 'b', '\n', '!'}, + new char[] {(char)128, (char)255, (char)1234}); + } + + @Test + public void testASCIIBitEscapes() { + // ISO646-US is another name for US-ASCII + // JDOM does not know that, but Java does. + checkBitEscape("ISO646-US", + new char[] {'a', 'b', '\n', '!'}, + new char[] {(char)128, (char)255, (char)1234}); + } + + @Test + public void test8BitEscapes() { + checkBitEscape("Latin1", + new char[] {'a', 'b', '\n', '!', (char)128, (char)255}, + new char[] {(char)1234}); + } + + @Test + public void test16BitEscapes() { + checkBitEscape("UTF-16", + new char[] {'a', 'b', '\n', '!', (char)128, (char)255, (char)1234}, + new char[] {(char)0xD800}); + } + + @Test + public void testCharsetEncodingEscapes() { + checkBitEscape("windows-1252", + new char[] {'a', 'b', '\n', '!', (char)127, (char)255}, + new char[] {(char)0xD800, (char)1234}); + } + + @Test + public void testIllegalEncodingEscapes() { + checkBitEscape("junk", + new char[] {'a', 'b', '\n', '!', (char)128, (char)255, (char)1234}, + new char[] {(char)0xD800}); + } + + private void checkBitEscape(String encoding, + char[] keep, char[] escape) { + Format form = Format.getPrettyFormat(); + form.setEncoding(encoding); + EscapeStrategy es = form.getEscapeStrategy(); + for (char ch : keep) { + assertFalse("Should Not Escape " + ch, es.shouldEscape(ch)); + } + for (char ch : escape) { + assertTrue("Should Escape " + ch, es.shouldEscape(ch)); + } + } + + private void checkTrim(String base, String both, String left, String right, String compact) { + assertEquals(both, Format.trimBoth(base)); + assertEquals(left, Format.trimLeft(base)); + assertEquals(right, Format.trimRight(base)); + assertEquals(compact, Format.compact(base)); + } + + @Test + public void testTrimming() { + checkTrim("", "", "", "", ""); + checkTrim(" ", "", "", "", ""); + checkTrim("x", "x", "x", "x", "x"); + checkTrim("foo", "foo", "foo", "foo", "foo"); + checkTrim(" \r\n ", "", "", "", ""); + checkTrim(" \rx\n ", "x", "x\n ", " \rx", "x"); + checkTrim(" \rx \t y\n ", "x \t y", "x \t y\n ", " \rx \t y", "x y"); + } + + private void checkEscapes(String eol, String base, String txt, String att) { + EscapeStrategy strategy = Format.getPrettyFormat().getEscapeStrategy(); + assertEquals(txt, Format.escapeText(strategy, eol, base)); + assertEquals(att, Format.escapeAttribute(strategy, base)); + } + + @Test + public void testEscapeText() { + checkEscapes(null, "", "", ""); + checkEscapes(null, " \n ", " \n ", " "); + checkEscapes("\r\n", " \n ", " \r\n ", " "); + checkEscapes(null, " \" \n ", " \" \n ", " " "); + checkEscapes("\r\n", " \" \n ", " \" \r\n ", " " "); + } + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestSAXOutputter.java b/test/src/java/org/jdom/test/cases/output/TestSAXOutputter.java new file mode 100644 index 0000000..3f7838a --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestSAXOutputter.java @@ -0,0 +1,1145 @@ +package org.jdom.test.cases.output; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.DeclHandler; +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.LocatorImpl; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.input.sax.SAXHandler; +import org.jdom.output.Format; +import org.jdom.output.JDOMLocator; +import org.jdom.output.LineSeparator; +import org.jdom.output.SAXOutputter; +import org.jdom.output.XMLOutputter2; +import org.jdom.output.support.AbstractSAXOutputProcessor; +import org.jdom.output.support.SAXOutputProcessor; +import org.jdom.test.util.UnitTestUtil; + + +@SuppressWarnings("javadoc") +public class TestSAXOutputter extends AbstractTestOutputter { + + private interface SAXSetup { + public SAXOutputter buildOutputter(SAXHandler handler); + } + + + + /** + * @param cr2xD + * @param padpreempty + * @param padpi + * @param forceexpand + * @param forceplatformeol + */ + public TestSAXOutputter() { + super(true, true, false, false, true); + } + + private void roundTrip(Document doc) { + roundTrip(null, doc); + } + + private void roundTrip(SAXSetup setup, Document doc) { + XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + // create a String representation of the input. + if (doc.hasRootElement()) { + UnitTestUtil.normalizeAttributes(doc.getRootElement()); + } + String expect = xout.outputString(doc); + + String actual = null; + try { + // convert the input to a SAX Stream + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = setup == null + ? new SAXOutputter(handler, handler, handler, handler, handler) + : setup.buildOutputter(handler); + + saxout.output(doc); + + Document backagain = handler.getDocument(); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + UnitTestUtil.normalizeAttributes(backagain.getRootElement()); + } + actual = xout.outputString(backagain); + } catch (JDOMException e) { + e.printStackTrace(); + fail("Failed to round-trip the document with exception: " + + e.getMessage() + "\n" + expect); + } + assertEquals(expect, actual); + } + + private void roundTrip(SAXSetup setup, Element emt) { + XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + Document tstdoc = new Document(); + tstdoc.addContent(emt.clone()); + String expect = xout.outputString(tstdoc); + + String actual = null; + try { + // convert the input to a SAX Stream + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = setup == null + ? new SAXOutputter(handler, handler, handler, handler, handler) + : setup.buildOutputter(handler); + + saxout.output(emt); + + Document backagain = handler.getDocument(); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + UnitTestUtil.normalizeAttributes(backagain.getRootElement()); + } + actual = xout.outputString(backagain); + } catch (JDOMException e) { + e.printStackTrace(); + fail("Failed to round-trip the document with exception: " + + e.getMessage() + "\n" + expect); + } + assertEquals(expect, actual); + } + + private void roundTripFragment(SAXSetup setup, List content) { + XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + Element root = new Element("root"); + Document tstdoc = new Document(root); + + for (Content c : content) { + root.addContent(c); + } + + String expect = xout.outputString(tstdoc); + + String actual = null; + try { + // convert the input to a SAX Stream + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = setup == null + ? new SAXOutputter(handler, handler, handler, handler, handler) + : setup.buildOutputter(handler); + + Locator loc = new LocatorImpl(); + handler.setDocumentLocator(loc); + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(content); + handler.endElement("", "root", "root"); + handler.endDocument(); + + Document backagain = handler.getDocument(); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + UnitTestUtil.normalizeAttributes(backagain.getRootElement()); + } + actual = xout.outputString(backagain); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to round-trip the document with exception: " + + e.getMessage() + "\n" + expect); + } + assertEquals(expect, actual); + } + + private void roundTripFragment(SAXSetup setup, Content content) { + XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + Element root = new Element("root"); + Document tstdoc = new Document(root); + + root.addContent(content); + + String expect = xout.outputString(tstdoc); + + String actual = null; + try { + // convert the input to a SAX Stream + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = setup == null + ? new SAXOutputter(handler, handler, handler, handler, handler) + : setup.buildOutputter(handler); + + Locator loc = new LocatorImpl(); + handler.setDocumentLocator(loc); + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(content); + handler.endElement("", "root", "root"); + handler.endDocument(); + + Document backagain = handler.getDocument(); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + UnitTestUtil.normalizeAttributes(backagain.getRootElement()); + } + actual = xout.outputString(backagain); + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to round-trip the document with exception: " + + e.getMessage() + "\n" + expect); + } + assertEquals(expect, actual); + } + + private void roundTrip(SAXSetup setup, List content) { + XMLOutputter2 xout = new XMLOutputter2(Format.getRawFormat()); + Document tstdoc = new Document(); + for (Object o : content) { + tstdoc.addContent((Content)o); + } + String expect = xout.outputString(tstdoc); + + String actual = null; + try { + // convert the input to a SAX Stream + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = setup == null + ? new SAXOutputter(handler, handler, handler, handler, handler) + : setup.buildOutputter(handler); + + saxout.output(content); + + Document backagain = handler.getDocument(); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + UnitTestUtil.normalizeAttributes(backagain.getRootElement()); + } + actual = xout.outputString(backagain); + } catch (JDOMException e) { + e.printStackTrace(); + fail("Failed to round-trip the document with exception: " + + e.getMessage() + "\n" + expect); + } + assertEquals(expect, actual); + } + + + @Test + public void testSAXOutputter() { + SAXOutputter saxout = new SAXOutputter(null, null, null, null); + assertTrue(null == saxout.getContentHandler()); + assertTrue(null == saxout.getDTDHandler()); + assertTrue(null == saxout.getLexicalHandler()); + assertTrue(null == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(null == saxout.getEntityResolver()); + } + + @Test + public void testSAXOutputterContentHandler() { + SAXOutputter saxout = new SAXOutputter(null); + assertTrue(null == saxout.getContentHandler()); + assertTrue(null == saxout.getDTDHandler()); + assertTrue(null == saxout.getLexicalHandler()); + assertTrue(null == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(null == saxout.getEntityResolver()); + + DefaultHandler2 handler = new DefaultHandler2(); + saxout = new SAXOutputter(handler); + assertTrue(handler == saxout.getContentHandler()); + assertTrue(null == saxout.getDTDHandler()); + assertTrue(null == saxout.getLexicalHandler()); + assertTrue(null == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(null == saxout.getEntityResolver()); + } + + @Test + public void testSAXOutputterContentHandlerErrorHandlerDTDHandlerEntityResolver() { + SAXOutputter saxout = new SAXOutputter(null, null, null, null); + assertTrue(null == saxout.getContentHandler()); + assertTrue(null == saxout.getDTDHandler()); + assertTrue(null == saxout.getLexicalHandler()); + assertTrue(null == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(null == saxout.getEntityResolver()); + + DefaultHandler2 handler = new DefaultHandler2(); + saxout = new SAXOutputter(handler, handler, handler, handler); + assertTrue(handler == saxout.getContentHandler()); + assertTrue(handler == saxout.getDTDHandler()); + assertTrue(null == saxout.getLexicalHandler()); + assertTrue(handler == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(handler == saxout.getEntityResolver()); + } + + @Test + public void testSAXOutputterContentHandlerErrorHandlerDTDHandlerEntityResolverLexicalHandler() { + SAXOutputter saxout = new SAXOutputter(null, null, null, null, null); + assertTrue(null == saxout.getContentHandler()); + assertTrue(null == saxout.getDTDHandler()); + assertTrue(null == saxout.getLexicalHandler()); + assertTrue(null == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(null == saxout.getEntityResolver()); + + DefaultHandler2 handler = new DefaultHandler2(); + saxout = new SAXOutputter(handler, handler, handler, handler, handler); + assertTrue(handler == saxout.getContentHandler()); + assertTrue(handler == saxout.getDTDHandler()); + assertTrue(handler == saxout.getLexicalHandler()); + assertTrue(handler == saxout.getErrorHandler()); + assertTrue(null == saxout.getDeclHandler()); + assertTrue(handler == saxout.getEntityResolver()); + } + + @Test + public void testContentHandler() { + ContentHandler handler = new DefaultHandler2(); + SAXOutputter saxout = new SAXOutputter(); + assertNull(saxout.getContentHandler()); + saxout.setContentHandler(handler); + assertTrue(saxout.getContentHandler() == handler); + } + + @Test + public void testErrorHandler() { + ErrorHandler handler = new DefaultHandler2(); + SAXOutputter saxout = new SAXOutputter(); + assertNull(saxout.getErrorHandler()); + saxout.setErrorHandler(handler); + assertTrue(saxout.getErrorHandler() == handler); + } + + @Test + public void testDTDHandler() { + DTDHandler handler = new DefaultHandler2(); + SAXOutputter saxout = new SAXOutputter(); + assertNull(saxout.getDTDHandler()); + saxout.setDTDHandler(handler); + assertTrue(saxout.getDTDHandler() == handler); + } + + @Test + public void testEntityResolver() { + EntityResolver handler = new DefaultHandler2(); + SAXOutputter saxout = new SAXOutputter(); + assertNull(saxout.getEntityResolver()); + saxout.setEntityResolver(handler); + assertTrue(saxout.getEntityResolver() == handler); + } + + @Test + public void testLexicalHandler() { + LexicalHandler handler = new DefaultHandler2(); + SAXOutputter saxout = new SAXOutputter(); + assertNull(saxout.getLexicalHandler()); + saxout.setLexicalHandler(handler); + assertTrue(saxout.getLexicalHandler() == handler); + } + + @Test + public void testDeclHandler() { + DeclHandler handler = new DefaultHandler2(); + SAXOutputter saxout = new SAXOutputter(); + assertNull(saxout.getDeclHandler()); + saxout.setDeclHandler(handler); + assertTrue(saxout.getDeclHandler() == handler); + } + + @Test + public void testReportNamespaceDeclarations() throws JDOMException { + final AtomicInteger includesxmlns = new AtomicInteger(0); + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void startElement(String arg0, String arg1, String arg2, Attributes arg3) throws SAXException { + super.startElement(arg0, arg1, arg2, arg3); + for (int i = arg3.getLength() - 1; i >= 0; i--) { + if (arg3.getQName(i).startsWith("xmlns")) { + includesxmlns.incrementAndGet(); + } + } + } + }; + Element root = new Element("root", Namespace.getNamespace("", "rooturi")); + root.addContent(new Element("child", Namespace.getNamespace("pfx", "childuri"))); + Document doc = new Document(root); + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + assertFalse(saxout.getReportNamespaceDeclarations()); + saxout.output(doc); + // we should not have reported a separate namespace xmlns:pfx="uri" + // thus, includesxmlns == 0 + assertTrue(includesxmlns.get() == 0); + saxout.setReportNamespaceDeclarations(true); + assertTrue(saxout.getReportNamespaceDeclarations()); + saxout.output(doc); + // we should have reported a separate namespace xmlns:pfx="childuri" + // and also xmlns="rooturi" + // thus, includesxmlns == 2 + assertTrue(includesxmlns.get() == 2); + } + + @Test + public void testReportDTDEvents() throws JDOMException { + final AtomicInteger includesdtd = new AtomicInteger(0); + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void notationDecl(String arg0, String arg1, String arg2) + throws SAXException { + super.notationDecl(arg0, arg1, arg2); + includesdtd.incrementAndGet(); + } + @Override + public void unparsedEntityDecl(String arg0, String arg1, + String arg2, String arg3) throws SAXException { + super.unparsedEntityDecl(arg0, arg1, arg2, arg3); + includesdtd.incrementAndGet(); + } + }; + DocType dt = new DocType("root"); + dt.setInternalSubset("" + + "" + + ""); + Document doc = new Document(new Element("root"), dt); + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + assertTrue(saxout.getReportDTDEvents()); + saxout.setReportDTDEvents(false); + assertFalse(saxout.getReportDTDEvents()); + saxout.output(doc); + // we should not have reported start/end DTD + // thus, includesdtd == 0 + assertTrue(includesdtd.get() == 0); + saxout.setReportDTDEvents(true); + assertTrue(saxout.getReportDTDEvents()); + saxout.output(doc); + // we should not have reported start/end DTD + // thus, includesdtd == 2 + assertTrue(includesdtd.get() == 2); + } + + @Test + public void testFeature() throws SAXNotRecognizedException, SAXNotSupportedException { + String NAMESPACES_SAX_FEATURE = "http://xml.org/sax/features/namespaces"; + String NS_PREFIXES_SAX_FEATURE = "http://xml.org/sax/features/namespace-prefixes"; + String VALIDATION_SAX_FEATURE = "http://xml.org/sax/features/validation"; + String JUNK_FEATURE = "http://xml.org/sax/features/junk"; + + + SAXOutputter saxout = new SAXOutputter(); + + assertFalse(saxout.getFeature(NS_PREFIXES_SAX_FEATURE)); + assertFalse(saxout.getReportNamespaceDeclarations()); + + assertTrue(saxout.getFeature(VALIDATION_SAX_FEATURE)); + assertTrue(saxout.getReportDTDEvents()); + + assertTrue(saxout.getFeature(NAMESPACES_SAX_FEATURE)); + + saxout.setReportDTDEvents(false); + saxout.setReportNamespaceDeclarations(true); + + assertTrue(saxout.getFeature(NS_PREFIXES_SAX_FEATURE)); + assertTrue(saxout.getReportNamespaceDeclarations()); + + assertFalse(saxout.getFeature(VALIDATION_SAX_FEATURE)); + assertFalse(saxout.getReportDTDEvents()); + + assertTrue(saxout.getFeature(NAMESPACES_SAX_FEATURE)); + + saxout.setFeature(NS_PREFIXES_SAX_FEATURE, false); + saxout.setFeature(VALIDATION_SAX_FEATURE, true); + + assertFalse(saxout.getFeature(NS_PREFIXES_SAX_FEATURE)); + assertFalse(saxout.getReportNamespaceDeclarations()); + + assertTrue(saxout.getFeature(VALIDATION_SAX_FEATURE)); + assertTrue(saxout.getReportDTDEvents()); + + assertTrue(saxout.getFeature(NAMESPACES_SAX_FEATURE)); + + // can re-set the feature to true. + saxout.setFeature(NAMESPACES_SAX_FEATURE, true); + + try { + saxout.setFeature(NAMESPACES_SAX_FEATURE, false); + fail("Should not be able to set Feature" + NAMESPACES_SAX_FEATURE + " to false"); + } catch (SAXNotSupportedException e) { + // good + } catch (Exception e) { + fail ("Expecting SAXNotSupportedException, not " + e.getClass().getName()); + } + + try { + saxout.getFeature(JUNK_FEATURE); + fail("Should not be able to get Feature" + JUNK_FEATURE); + } catch (SAXNotRecognizedException e) { + // good + } catch (Exception e) { + fail ("Expecting SAXNotRecognizedException, not " + e.getClass().getName()); + } + + try { + saxout.setFeature(JUNK_FEATURE, true); + fail("Should not be able to set Feature" + JUNK_FEATURE); + } catch (SAXNotRecognizedException e) { + // good + } catch (Exception e) { + fail ("Expecting SAXNotRecognizedException, not " + e.getClass().getName()); + } + + } + + @Test + public void testProperty() throws SAXNotRecognizedException, SAXNotSupportedException { + String lhs = "http://xml.org/sax/properties/lexical-handler"; + String dhs = "http://xml.org/sax/properties/declaration-handler"; + String jhs = "http://xml.org/sax/properties/junk-handler"; + SAXOutputter saxout = new SAXOutputter(); + DefaultHandler2 handler = new DefaultHandler2(); + assertNull(saxout.getProperty(lhs)); + assertNull(saxout.getProperty(dhs)); + assertNull(saxout.getLexicalHandler()); + assertNull(saxout.getDeclHandler()); + + saxout.setDeclHandler(handler); + saxout.setLexicalHandler(handler); + assertTrue(handler == saxout.getProperty(lhs)); + assertTrue(handler == saxout.getProperty(dhs)); + assertTrue(handler == saxout.getLexicalHandler()); + assertTrue(handler == saxout.getDeclHandler()); + + saxout.setDeclHandler(null); + saxout.setLexicalHandler(null); + assertNull(saxout.getProperty(lhs)); + assertNull(saxout.getProperty(dhs)); + + saxout.setProperty(lhs, handler); + saxout.setProperty(dhs, handler); + assertTrue(handler == saxout.getProperty(lhs)); + assertTrue(handler == saxout.getProperty(dhs)); + assertTrue(handler == saxout.getLexicalHandler()); + assertTrue(handler == saxout.getDeclHandler()); + + try { + saxout.getProperty(jhs); + fail("Should not be able to get " + jhs); + } catch (SAXNotRecognizedException e) { + // good + } catch (Exception e) { + fail ("Expecting SAXNotRecognizedException, not " + e.getClass().getName()); + } + try { + saxout.setProperty(jhs, new Object()); + fail("Should not be able to set " + jhs); + } catch (SAXNotRecognizedException e) { + // good + } catch (Exception e) { + fail ("Expecting SAXNotRecognizedException, not " + e.getClass().getName()); + } + } + + @Test + public void testSAXOutputDocumentSimple() { + Document doc = new Document(new Element("root")); + roundTrip(doc); + } + + @Test + public void testSAXOutputDocumentFull() { + Document doc = new Document(); + doc.addContent(new DocType("root")); + doc.addContent(new Comment("This is a document")); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + Element e = new Element("root"); + e.addContent(new EntityRef("ref")); + doc.addContent(e); + roundTrip(doc); + } + + @Test + public void testOutputDocumentRootAttNS() { + Document doc = new Document(); + Element e = new Element("root"); + e.setAttribute(new Attribute("att", "val", Namespace.getNamespace("ans", "mynamespace"))); + doc.addContent(e); + roundTrip(doc); + } + + @Test + public void testOutputDocumentFullNoLexical() throws JDOMException { + Document doc = new Document(); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + Element e = new Element("root"); + doc.addContent(e); + e.addContent(new Text("text")); + e.addContent(new EntityRef("ref")); + + // the Lexical handler is what helps track comments, + // and identifies CDATA sections. + // as a result the output of CDATA structures appears as plain text. + // and comments are dropped.... + e.addContent(new Text("cdata")); + String expect = new XMLOutputter2().outputString(doc); + e.removeContent(2); + e.addContent(new CDATA("cdata")); + e.addContent(new Comment("This is a document")); + + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler); + saxout.output(doc); + + Document act = handler.getDocument(); + String actual = new XMLOutputter2().outputString(act); + + assertEquals(expect, actual); + + } + + @Test + public void testOutputDocumentNoErrorHandler() { + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void error(SAXParseException arg0) throws SAXException { + throw new SAXException ("foo!"); + } + }; + + SAXOutputter saxout = new SAXOutputter(handler); + + try { + saxout.outputFragment(new DocType("rootemt")); + fail("SHould have exception!"); + } catch (JDOMException e) { + assertTrue(e.getMessage().indexOf("rootemt") >= 0); + } + + saxout.setErrorHandler(handler); + + try { + saxout.outputFragment(new DocType("rootemt")); + fail("SHould have exception!"); + } catch (JDOMException e) { + assertTrue(e.getMessage().endsWith("foo!")); + } + + } + + @Test + public void testOutputDocumentAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + Document doc = new Document(emt); + roundTrip(doc); + } + + @Test + public void testOutputDocumentNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addContent(new Element("child", Namespace.getNamespace("", "childuri"))); + Document doc = new Document(emt); + roundTrip(doc); + } + + @Test + public void testSAXOutputList() { + List list = new ArrayList(); + list.add(new ProcessingInstruction("jdomtest", "")); + list.add(new Comment("comment")); + list.add(new Element("root")); + roundTrip(null, list); + } + + @Test + public void testOutputElementAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + emt.setAttribute(new Attribute("attx", "valx", AttributeType.UNDECLARED)); + roundTrip(null, emt); + } + + @Test + public void testSAXOutputElementNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addContent(new Element("child", Namespace.getNamespace("", "childns"))); + roundTrip(null, emt); + } + + @Test + public void testOutputFragmentList() { + List list = new ArrayList(); + list.add(new ProcessingInstruction("jdomtest", "")); + list.add(new Comment("comment")); + list.add(new CDATA("foo")); + list.add(new Element("root")); + list.add(new Text("bar")); + roundTripFragment(null, list); + } + + @Test + public void testOutputFragmentContent() { + roundTripFragment(null, new ProcessingInstruction("jdomtest", "")); + roundTripFragment(null, new Comment("comment")); + roundTripFragment(null, new CDATA("foo")); + roundTripFragment(null, new Element("root")); + roundTripFragment(null, new Text("bar")); + } + + @Test + public void testOutputNullContent() throws JDOMException { + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void startDocument() throws SAXException { + throw new SAXException("SHould not be reaching this, ever"); + } + }; + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + + Document doc = null; + Element emt = null; + List list = null; + List empty = new ArrayList(); + saxout.output(doc); + saxout.output(emt); + saxout.output(list); + saxout.output(empty); + saxout.outputFragment(emt); + saxout.outputFragment(list); + saxout.outputFragment(empty); + } + + @SuppressWarnings("deprecation") + @Test + public void testGetLocator() throws JDOMException { + SAXOutputter saxout = new SAXOutputter(); + final AtomicBoolean coderan = new AtomicBoolean(false); + assertTrue(saxout.getLocator() == null); + DefaultHandler2 handler = new DefaultHandler2() { + JDOMLocator locator = null; + @Override + public void setDocumentLocator(Locator loc) { + if (loc instanceof JDOMLocator) { + locator = (JDOMLocator)loc; + } else { + fail ("We excpected the locator to be a JDOMLocator, not " + loc); + } + } + @Override + public void endElement(String uri, String localname, String qname) { + assertNotNull(locator); + assertTrue(locator.getNode() != null); + assertTrue(locator.getNode() instanceof Element); + Element emt = (Element)locator.getNode(); + assertEquals(emt.getName(), localname); + coderan.set(true); + } + }; + saxout.setContentHandler(handler); + saxout.setDTDHandler(handler); + saxout.setEntityResolver(handler); + saxout.setLexicalHandler(handler); + saxout.setDeclHandler(handler); + saxout.setErrorHandler(handler); + Document doc = new Document(new Element("root")); + saxout.output(doc); + assertTrue(coderan.get()); + } + + + + + @Test + public void testGetSetFormat() { + SAXOutputter sout = new SAXOutputter(); + Format def = sout.getFormat(); + assertTrue(def != null); + Format f = Format.getPrettyFormat(); + sout.setFormat(f); + assertTrue(f == sout.getFormat()); + sout.setFormat(null); + TestFormat.checkEquals(def, sout.getFormat()); + } + + @Test + public void testGetSetDOMOutputProcessor() { + SAXOutputProcessor dop = new AbstractSAXOutputProcessor() { + // nothing. + }; + + SAXOutputter dout = new SAXOutputter(); + SAXOutputProcessor def = dout.getSAXOutputProcessor(); + assertTrue(def != null); + dout.setSAXOutputProcessor(dop); + assertTrue(dop == dout.getSAXOutputProcessor()); + dout.setSAXOutputProcessor(null); + assertEquals(def, dout.getSAXOutputProcessor()); + } + + @Override + public String outputDocumentAsString(Format format, Document doc) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + try { + saxout.output(doc); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument()); + } + + @Override + public String outputDocTypeAsString(Format format, DocType doctype) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + List list = new ArrayList(1); + list.add(doctype); + //list.add(new Element("root")); + + try { + saxout.output(list); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getDocType()); + } + + @Override + public String outputElementAsString(Format format, Element element) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + saxout.output(element); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement()); + } + + @Override + public String outputListAsString(Format format, List list) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(list); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputCDataAsString(Format format, CDATA cdata) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(cdata); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputTextAsString(Format format, Text text) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(text); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputCommentAsString(Format format, Comment comment) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(comment); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputPIAsString(Format format, ProcessingInstruction pi) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(pi); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputEntityRefAsString(Format format, EntityRef entity) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + handler.startDocument(); + handler.startElement("", "root", "root", new AttributesImpl()); + saxout.outputFragment(entity); + handler.endElement("", "root", "root"); + handler.endDocument(); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement().getContent()); + } + + @Override + public String outputElementContentString(Format format, Element element) { + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(null, format, handler, handler, + handler, handler, handler); + + try { + saxout.outputFragment(element.getContent()); + } catch (Exception e) { + throw new IllegalStateException("Could not output", e); + } + XMLOutputter2 xout = new XMLOutputter2(); + xout.getFormat().setLineSeparator(LineSeparator.NL); + return xout.outputString(handler.getDocument().getRootElement()); + } + + + + + @Test + @Override + @Ignore + public void testOutputElementExpandEmpty() { + // Can't control expand in SAXOutputter. + } + + @Test + @Override + @Ignore + public void testOutputElementMultiAllWhiteExpandEmpty() { + // Can't control expand in SAXOutputter. + } + + @Test + @Ignore + @Override + public void testDocTypeSimpleISS() { + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + @Test + @Ignore + @Override + public void testDocTypePublicSystemID() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + @Test + @Ignore + @Override + public void testDocTypePublicSystemIDISS() { + //Cannot test with a SystemID because it needs to be resolved/referenced + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + @Test + @Ignore + @Override + public void testDocTypeSystemID() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + @Test + @Ignore + @Override + public void testDocTypeSystemIDISS() { + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + @Test + @Override + @Ignore + public void testDocumentDocType() { + // override because we can't control whitespace outside of the root element + } + + + @Test + @Override + @Ignore + public void testOutputDocTypeInternalSubset() { + //Cannot preserve internal subset in DOCTYPE through the round-trip test. + } + + + @Test + @Override + @Ignore + public void testOutputDocTypeSystem() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + @Test + @Override + @Ignore + public void testOutputDocTypePublic() { + // cannot test with a non-resolved publicID + } + + @Test + @Override + @Ignore + public void testOutputDocTypePublicSystem() { + //Cannot test with a SystemID because it needs to be resolved/referenced + } + + + @Test + @Override + @Ignore + public void testOutputDocumentOmitEncoding() { + // Cannot test for formatting outside of root element + } + + @Test + @Override + @Ignore + public void testOutputDocumentOmitDeclaration() { + // Cannot test for formatting outside of root element + } + + @Test + public void testNoNamespaceIssue60 () throws JDOMException { + Document doc = new Document(); + Namespace ns = Namespace.getNamespace("myurl"); + Element root = new Element("root", ns); + Element child = new Element("child", ns); + root.addContent(child); + doc.setRootElement(root); + final String[] count = new String[1]; + + child.setAttribute("att", "val"); + + ContentHandler ch = new DefaultHandler2() { + @Override + public void startPrefixMapping(String pfx, String uri) + throws SAXException { + if ("".equals(pfx) && "".equals(uri)) { + fail("Should not be firing xmlns=\"\""); + } + if (!"".equals(pfx)) { + fail("we should not have prefix " + pfx); + } + if (count[0] != null) { + fail("we should not have multiple mappings " + pfx + " -> " + uri); + } + count[0] = uri; + } + }; + SAXOutputter saxout = new SAXOutputter(ch); + saxout.output(doc); + assertTrue("myurl".equals(count[0])); + } + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestStAXEventOutputter.java b/test/src/java/org/jdom/test/cases/output/TestStAXEventOutputter.java new file mode 100644 index 0000000..9775acb --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestStAXEventOutputter.java @@ -0,0 +1,928 @@ +package org.jdom.test.cases.output; + +import static org.jdom.test.util.UnitTestUtil.failException; +import static org.jdom.test.util.UnitTestUtil.normalizeAttributes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLEventWriter; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.util.XMLEventConsumer; + +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.ext.DefaultHandler2; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.IllegalDataException; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.UncheckedJDOMFactory; +import org.jdom.input.SAXBuilder; +import org.jdom.input.StAXStreamBuilder; +import org.jdom.input.sax.SAXHandler; +import org.jdom.input.stax.DefaultStAXFilter; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.support.AbstractStAXEventProcessor; +import org.jdom.output.support.StAXEventProcessor; +import org.jdom.output.SAXOutputter; +import org.jdom.output.StAXEventOutputter; +import org.jdom.output.XMLOutputter2; + +@SuppressWarnings("javadoc") +public final class TestStAXEventOutputter extends AbstractTestOutputter { + + private final static XMLOutputFactory soutfactory = XMLOutputFactory.newInstance(); + private final static XMLInputFactory sinfactory = XMLInputFactory.newInstance(); + private final static XMLEventFactory seventfactory = XMLEventFactory.newInstance(); + + private static final class OutWrapper { + private final StringWriter swriter = new StringWriter(); + + private final StAXEventOutputter stax; + private final XMLEventWriter xwriter; + private int from = 0, to = -1; + + public OutWrapper(Format format) { + try { + xwriter = soutfactory.createXMLEventWriter(swriter); + stax = new StAXEventOutputter(format); + } catch (Exception xse) { + throw new IllegalStateException("Cannot construct: See Cause", xse); + } + } + + @Override + public String toString() { + return to >= 0 ? swriter.getBuffer().substring(from, to) : + swriter.getBuffer().substring(from); + } + + + + public StAXEventOutputter getStax() { + return stax; + } + + public void close() { + try { + xwriter.close(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + } + + public XMLEventWriter getStream() { + return xwriter; + } + + public void setDocumentMarkFrom() { + try { + xwriter.add(seventfactory.createStartDocument()); + xwriter.add(seventfactory.createCharacters("")); + xwriter.flush(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + from = swriter.getBuffer().length(); + } + + public void setDocumentMarkTo() { + try { + xwriter.add(seventfactory.createCharacters("")); + xwriter.flush(); + to = swriter.getBuffer().length(); + xwriter.add(seventfactory.createEndDocument()); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + } + + public void setElementMarkFrom() { + try { + xwriter.add(seventfactory.createStartDocument()); + xwriter.add(seventfactory.createStartElement("", "", "root")); + xwriter.add(seventfactory.createCharacters("")); + xwriter.flush(); + from = swriter.getBuffer().length(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + } + + public void setElementMarkTo() { + try { + xwriter.add(seventfactory.createCharacters("")); + xwriter.flush(); + to = swriter.getBuffer().length(); + xwriter.add(seventfactory.createEndElement("", "", "root")); + xwriter.add(seventfactory.createEndDocument()); + xwriter.flush(); + xwriter.close(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + } + + } + + + private static final class EventStore implements XMLEventConsumer { + private final ArrayList store = new ArrayList(); + private final String encoding; + + EventStore(String enc) { + encoding = enc; + } + + @Override + public void add(XMLEvent event) throws XMLStreamException { + store.add(event); + } + + @Override + public String toString() { + ByteArrayOutputStream sw = new ByteArrayOutputStream(); + try { + XMLEventWriter xew = soutfactory.createXMLEventWriter(sw, encoding); + for (XMLEvent x : store) { + xew.add(x); + } + xew.flush(); + xew.close(); + return new String(sw.toByteArray()); + } catch (XMLStreamException e) { + throw new IllegalStateException("Can't get toString...", e); + } + + } + } + + public TestStAXEventOutputter() { + super(false, false, true, true, false); + } + + + @Override + public String outputDocumentAsString(Format format, Document doc) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStax().output(doc, ow.getStream()); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + + + + @Override + public String outputDocTypeAsString(Format format, DocType doctype) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setDocumentMarkFrom(); + ow.getStax().output(doctype, ow.getStream()); + ow.setDocumentMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputElementAsString(Format format, Element element) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setDocumentMarkFrom(); + ow.getStax().output(element, ow.getStream()); + ow.setDocumentMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputListAsString(Format format, List list) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setElementMarkFrom(); + ow.getStax().output(list, ow.getStream()); + ow.setElementMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputCDataAsString(Format format, CDATA cdata) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setElementMarkFrom(); + ow.getStax().output(cdata, ow.getStream()); + ow.setElementMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputTextAsString(Format format, Text text) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setElementMarkFrom(); + ow.getStax().output(text, ow.getStream()); + ow.setElementMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputCommentAsString(Format format, Comment comment) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setDocumentMarkFrom(); + ow.getStax().output(comment, ow.getStream()); + ow.setDocumentMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputPIAsString(Format format, ProcessingInstruction pi) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setDocumentMarkFrom(); + ow.getStax().output(pi, ow.getStream()); + ow.setDocumentMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputEntityRefAsString(Format format, EntityRef entity) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setElementMarkFrom(); + ow.getStax().output(entity, ow.getStream()); + ow.setElementMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputElementContentString(Format format, Element element) { + OutWrapper ow = new OutWrapper(format); + try { + ow.setElementMarkFrom(); + ow.getStax().outputElementContent(element, ow.getStream()); + ow.setElementMarkTo(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public void testOutputDocumentOmitDeclaration() { + // skip this test. + } + + @Test @Ignore + public void test_HighSurrogatePair() throws XMLStreamException, IOException, JDOMException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + EventStore es = new EventStore("ISO-8859-1"); + outputter.output(doc, es); + String xml = es.toString(); + assertEquals("\r\n" + + "�� ��\r\n", xml); + } + + @Test @Ignore + public void test_HighSurrogatePairDecimal() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + EventStore es = new EventStore("ISO-8859-1"); + outputter.output(doc, es); + String xml = es.toString(); + assertEquals("\r\n" + + "�� ��\r\n", xml); + } + + @Test @Ignore + public void test_HighSurrogateAttPair() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + EventStore es = new EventStore("ISO-8859-1"); + outputter.output(doc, es); + String xml = es.toString(); + assertEquals("\r\n" + + "\r\n", xml); + } + + @Test @Ignore + public void test_HighSurrogateAttPairDecimal() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + EventStore es = new EventStore("ISO-8859-1"); + outputter.output(doc, es); + String xml = es.toString(); + assertEquals("\r\n" + + "\r\n", xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped + @Test @Ignore + public void test_RawSurrogatePair() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + EventStore es = new EventStore("ISO-8859-1"); + outputter.output(doc, es); + String xml = es.toString(); + assertEquals("\r\n" + + "��\r\n", xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped, when UTF-8 too + @Test + @Ignore + // TODO + public void test_RawSurrogatePairUTF8() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("UTF-8"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + EventStore es = new EventStore("UTF-8"); + outputter.output(doc, es); + String xml = es.toString(); + assertEquals("" + + "\uD800\uDC00", xml); + } + + // Construct illegal XML and check if the parser notices + @Test + public void test_ErrorSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + try { + doc.getRootElement().setText("\uD800\uDBFF"); + fail("Illegal surrogate pair should have thrown an exception"); + } + catch (IllegalDataException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + // Manually construct illegal XML and make sure the outputter notices + @Test + @Ignore + // TODO + public void test_ErrorSurrogatePairOutput() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Text t = new UncheckedJDOMFactory().text("\uD800\uDBFF"); + doc.getRootElement().setContent(t); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXEventOutputter outputter = new StAXEventOutputter(format); + try { + EventStore es = new EventStore("ISO-8859-1"); + outputter.output(doc, es); + fail("Illegal surrogate pair output should have thrown an exception"); + } + catch (XMLStreamException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + + @Test + public void testXMLOutputter() { + StAXEventOutputter out = new StAXEventOutputter(); + TestFormat.checkEquals(out.getFormat(), Format.getRawFormat()); + } + + + @Test + public void testXMLOutputterFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + StAXEventOutputter out = new StAXEventOutputter(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + +// @Test +// public void testXMLOutputterXMLOutputter() { +// Format mine = Format.getCompactFormat(); +// StAXEventProcessor xoutp = new StAXEventOutputter().getStAXStream(); +// mine.setEncoding("US-ASCII"); +// // double-construct it. +// StAXEventOutputter out = new StAXEventOutputter(); +// TestFormat.checkEquals(mine, out.getFormat()); +// assertTrue(xoutp == out.getXMLOutputProcessor()); +// } + + @Test + public void testXMLOutputterXMLOutputProcessor() { + StAXEventProcessor xoutp = new AbstractStAXEventProcessor() { + // nothing; + }; + // double-constrcut it. + StAXEventOutputter out = new StAXEventOutputter(xoutp); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + assertTrue(xoutp == out.getStAXStream()); + } + + @Test + public void testFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + // double-constcut it. + StAXEventOutputter out = new StAXEventOutputter(); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + out.setFormat(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + + @Test + public void testXMLOutputProcessor() { + StAXEventProcessor xoutp = new AbstractStAXEventProcessor() { + // nothing; + }; + // double-constcut it. + StAXEventOutputter out = new StAXEventOutputter(); + StAXEventProcessor xop = out.getStAXStream(); + out.setStAXEventProcessor(xoutp); + assertTrue(xoutp != xop); + assertTrue(xoutp == out.getStAXStream()); + } + + @Test + public void testTrimFullWhite() throws XMLStreamException { + // See issue #31. + // https://github.com/hunterhacker/jdom/issues/31 + // This tests should pass when issue 31 is resolved. + Element root = new Element("root"); + root.addContent(new Text(" ")); + root.addContent(new Text("x")); + root.addContent(new Text(" ")); + Format mf = Format.getRawFormat(); + mf.setTextMode(TextMode.TRIM_FULL_WHITE); + StAXEventOutputter xout = new StAXEventOutputter(mf); + EventStore es = new EventStore("UTF-8"); + xout.output(root, es); + assertEquals(" x ", es.toString()); + } + + @Test + public void testClone() { + StAXEventOutputter xo = new StAXEventOutputter(); + assertTrue(xo != xo.clone()); + } + + @Test + public void testToString() { + Format fmt = Format.getCompactFormat(); + fmt.setLineSeparator("\n\t "); + StAXEventOutputter out = new StAXEventOutputter(fmt); + assertNotNull(out.toString()); + } + + /* + * The following are borrowed from the TestSAXOutputter + * The effect is that we compare the StAX string output with the re-parsed + * value of the input. + */ + + private void roundTripDocument(Document doc) { + StAXEventOutputter xout = new StAXEventOutputter(Format.getRawFormat()); + // create a String representation of the input. + if (doc.hasRootElement()) { + normalizeAttributes(doc.getRootElement()); + } + + try { + EventStore xsw = new EventStore("UTF-8"); + xout.output(doc, xsw); + String expect = xsw.toString(); + + // convert the input to a SAX Stream + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + char[] chars = expect.toCharArray(); + CharArrayReader car = new CharArrayReader(chars); + XMLStreamReader xsr = sinfactory.createXMLStreamReader(car); + + Document backagain = sbuilder.build(xsr); + xsr.close(); + + // get a String representation of the round-trip. + if (backagain.hasRootElement()) { + normalizeAttributes(backagain.getRootElement()); + } + xsw = new EventStore("UTF-8"); + xout.output(backagain, xsw); + String actual = xsw.toString(); + + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + } + + private void roundTripElement(Element emt) { + + try { + StAXEventOutputter xout = new StAXEventOutputter(Format.getRawFormat()); + + EventStore xsw = new EventStore("UTF-8"); + xout.output(emt, xsw); + String expect = xsw.toString(); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + XMLStreamReader xsr = sinfactory.createXMLStreamReader(new StringReader(expect)); + assertTrue(xsr.getEventType() == XMLStreamConstants.START_DOCUMENT); + assertTrue(xsr.hasNext()); + xsr.next(); + + Element backagain = (Element)sbuilder.fragment(xsr); + + // convert the input to a SAX Stream + + xsw = new EventStore("UTF-8"); + xout.output(backagain, xsw); + + String actual = xsw.toString(); + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + } + + private void roundTripFragment(List content) { + try { + StAXEventOutputter xout = new StAXEventOutputter(Format.getRawFormat()); + + EventStore xsw = new EventStore("UTF-8"); + xout.output(content, xsw); + String expect = xsw.toString(); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + XMLStreamReader xsr = sinfactory.createXMLStreamReader(new StringReader(expect)); +// assertTrue(xsr.getEventType() == XMLStreamConstants.START_DOCUMENT); +// assertTrue(xsr.hasNext()); +// xsr.next(); + + List backagain = sbuilder.buildFragments(xsr, new DefaultStAXFilter()); + + // convert the input to a SAX Stream + + xsw = new EventStore("UTF-8"); + xout.output(backagain, xsw); + + String actual = xsw.toString(); + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + + } + + private void roundTripFragment(Content content) { + try { + StAXEventOutputter xout = new StAXEventOutputter(Format.getRawFormat()); + + EventStore xsw = new EventStore("UTF-8"); + switch(content.getCType()) { + case CDATA : + xout.output((CDATA)content, xsw); + break; + case Text: + xout.output((Text)content, xsw); + break; + case Comment: + xout.output((Comment)content, xsw); + break; + case DocType: + xout.output((DocType)content, xsw); + break; + case Element: + xout.output((Element)content, xsw); + break; + case EntityRef: + xout.output((EntityRef)content, xsw); + break; + case ProcessingInstruction: + xout.output((ProcessingInstruction)content, xsw); + break; + default: + throw new IllegalStateException(content.getCType().toString()); + } + String expect = xsw.toString(); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + Content backagain = sbuilder.fragment( + sinfactory.createXMLStreamReader(new StringReader(expect))); + + // convert the input to a SAX Stream + + xsw = new EventStore("UTF-8"); + switch(content.getCType()) { + case CDATA : + xout.output((CDATA)backagain, xsw); + break; + case Text: + xout.output((Text)backagain, xsw); + break; + case Comment: + xout.output((Comment)backagain, xsw); + break; + case DocType: + xout.output((DocType)backagain, xsw); + break; + case Element: + xout.output((Element)backagain, xsw); + break; + case EntityRef: + xout.output((EntityRef)backagain, xsw); + break; + case ProcessingInstruction: + xout.output((ProcessingInstruction)backagain, xsw); + break; + default: + throw new IllegalStateException(backagain.getCType().toString()); + } + + String actual = xsw.toString(); + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + } + + @Test + public void testRTOutputDocumentSimple() { + Document doc = new Document(new Element("root")); + roundTripDocument(doc); + } + + @Test + // TODO + @Ignore + public void testRTOutputDocumentFull() { + Document doc = new Document(); + DocType dt = new DocType("root"); + dt.setInternalSubset(" "); + doc.addContent(dt); + doc.addContent(new Comment("This is a document")); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + Element e = new Element("root"); + e.addContent(new EntityRef("ref")); + doc.addContent(e); + roundTripDocument(doc); + } + + @Test + public void testOutputDocumentRootAttNS() { + Document doc = new Document(); + Element e = new Element("root"); + e.setAttribute(new Attribute("att", "val", Namespace.getNamespace("ans", "mynamespace"))); + doc.addContent(e); + roundTripDocument(doc); + } + + @Test + public void testOutputDocumentFullNoLexical() throws JDOMException { + Document doc = new Document(); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + Element e = new Element("root"); + doc.addContent(e); + e.addContent(new Text("text")); + e.addContent(new EntityRef("ref")); + + // the Lexical handler is what helps track comments, + // and identifies CDATA sections. + // as a result the output of CDATA structures appears as plain text. + // and comments are dropped.... + e.addContent(new Text("cdata")); + String expect = new XMLOutputter2().outputString(doc); + e.removeContent(2); + e.addContent(new CDATA("cdata")); + e.addContent(new Comment("This is a document")); + + SAXHandler handler = new SAXHandler(); + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler); + saxout.output(doc); + + Document act = handler.getDocument(); + String actual = new XMLOutputter2().outputString(act); + + assertEquals(expect, actual); + + } + + @Test + public void testOutputDocumentNoErrorHandler() { + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void error(SAXParseException arg0) throws SAXException { + throw new SAXException ("foo!"); + } + }; + + SAXOutputter saxout = new SAXOutputter(handler); + + try { + saxout.outputFragment(new DocType("rootemt")); + fail("SHould have exception!"); + } catch (JDOMException e) { + assertTrue(e.getMessage().indexOf("rootemt") >= 0); + } + + saxout.setErrorHandler(handler); + + try { + saxout.outputFragment(new DocType("rootemt")); + fail("SHould have exception!"); + } catch (JDOMException e) { + assertTrue(e.getMessage().endsWith("foo!")); + } + + } + + @Test + public void testOutputDocumentAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + Document doc = new Document(emt); + roundTripDocument(doc); + } + + @Test + public void testOutputDocumentNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addContent(new Element("child", Namespace.getNamespace("", "childuri"))); + Document doc = new Document(emt); + roundTripDocument(doc); + } + + @Test + public void testRTOutputList() { + List list = new ArrayList(); + list.add(new ProcessingInstruction("jdomtest", "")); + list.add(new Comment("comment")); + list.add(new Element("root")); + roundTripFragment(list); + } + + @Test + public void testOutputElementAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + emt.setAttribute(new Attribute("attx", "valx", AttributeType.UNDECLARED)); + roundTripElement(emt); + } + + @Test + public void testRTOutputElementNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addContent(new Element("child", Namespace.getNamespace("", "childns"))); + roundTripElement(emt); + } + + @Test + @Ignore + public void testOutputFragmentList() { + List list = new ArrayList(); + list.add(new ProcessingInstruction("jdomtest", "")); + list.add(new Comment("comment")); + list.add(new CDATA("foo")); + list.add(new Element("root")); + list.add(new Text("bar")); + roundTripFragment(list); + } + + @Test + @Ignore + public void testOutputFragmentContent() { + roundTripFragment(new ProcessingInstruction("jdomtest", "")); + roundTripFragment(new Comment("comment")); + roundTripFragment(new CDATA("foo")); + roundTripFragment(new Element("root")); + roundTripFragment(new Text("bar")); + } + + @Test + public void testOutputNullContent() throws JDOMException { + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void startDocument() throws SAXException { + throw new SAXException("SHould not be reaching this, ever"); + } + }; + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + + Document doc = null; + Element emt = null; + List list = null; + List empty = new ArrayList(); + saxout.output(doc); + saxout.output(emt); + saxout.output(list); + saxout.output(empty); + saxout.outputFragment(emt); + saxout.outputFragment(list); + saxout.outputFragment(empty); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestStAXStreamOutputter.java b/test/src/java/org/jdom/test/cases/output/TestStAXStreamOutputter.java new file mode 100644 index 0000000..601e701 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestStAXStreamOutputter.java @@ -0,0 +1,869 @@ +package org.jdom.test.cases.output; + +import static org.jdom.test.util.UnitTestUtil.failException; +import static org.jdom.test.util.UnitTestUtil.normalizeAttributes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; + +import org.junit.Ignore; +import org.junit.Test; +import org.xml.sax.SAXException; +import org.xml.sax.ext.DefaultHandler2; + +import org.jdom.Attribute; +import org.jdom.AttributeType; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.IllegalDataException; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.UncheckedJDOMFactory; +import org.jdom.input.SAXBuilder; +import org.jdom.input.StAXStreamBuilder; +import org.jdom.input.stax.DefaultStAXFilter; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.SAXOutputter; +import org.jdom.output.StAXStreamOutputter; +import org.jdom.output.support.AbstractStAXStreamProcessor; +import org.jdom.output.support.StAXStreamProcessor; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public final class TestStAXStreamOutputter extends AbstractTestOutputter { + + + private static final XMLOutputFactory soutfactory = XMLOutputFactory.newInstance(); + private static final XMLInputFactory sinfactory = XMLInputFactory.newInstance(); + + private static final class OutWrapper { + private final StringWriter swriter = new StringWriter(); + private final StAXStreamOutputter stax; + private final XMLStreamWriter xwriter; + private int from = 0, to = -1; + + public OutWrapper(Format format) { + try { + xwriter = soutfactory.createXMLStreamWriter(swriter); + stax = new StAXStreamOutputter(format); + } catch (Exception xse) { + throw new IllegalStateException("Cannot construct: See Cause", xse); + } + } + + public void setMarkFrom() { + try { + xwriter.flush(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + from = swriter.getBuffer().length(); + } + + public void setMarkTo() { + try { + xwriter.flush(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + to = swriter.getBuffer().length(); + } + + @Override + public String toString() { + return to >= 0 ? swriter.getBuffer().substring(from, to) : + swriter.getBuffer().substring(from); + } + + + + public StAXStreamOutputter getStax() { + return stax; + } + + public void close() { + try { + xwriter.close(); + } catch (XMLStreamException e) { + throw new IllegalStateException("Cannot flush(): See Cause", e); + } + } + + public XMLStreamWriter getStream() { + return xwriter; + } + + } + + public TestStAXStreamOutputter() { + super(false, false, false, false, false); + } + + @Override + public String outputDocumentAsString(Format format, Document doc) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStax().output(doc, ow.getStream()); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputDocTypeAsString(Format format, DocType doctype) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.setMarkFrom(); + ow.getStax().output(doctype, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputElementAsString(Format format, Element element) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.setMarkFrom(); + ow.getStax().output(element, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputListAsString(Format format, List list) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.getStream().writeStartElement("root"); + ow.getStream().writeCharacters(""); + ow.setMarkFrom(); + ow.getStax().output(list, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndElement(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputCDataAsString(Format format, CDATA cdata) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.getStream().writeStartElement("root"); + ow.getStream().writeCharacters(""); + ow.setMarkFrom(); + ow.getStax().output(cdata, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndElement(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputTextAsString(Format format, Text text) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.getStream().writeStartElement("root"); + ow.getStream().writeCharacters(""); + ow.setMarkFrom(); + ow.getStax().output(text, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndElement(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputCommentAsString(Format format, Comment comment) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.setMarkFrom(); + ow.getStax().output(comment, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputPIAsString(Format format, ProcessingInstruction pi) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.setMarkFrom(); + ow.getStax().output(pi, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputEntityRefAsString(Format format, EntityRef entity) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.getStream().writeStartElement("root"); + ow.getStream().writeCharacters(""); + ow.setMarkFrom(); + ow.getStax().output(entity, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndElement(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Override + public String outputElementContentString(Format format, Element element) { + OutWrapper ow = new OutWrapper(format); + try { + ow.getStream().writeStartDocument(); + ow.getStream().writeStartElement("root"); + ow.getStream().writeCharacters(""); + ow.setMarkFrom(); + ow.getStax().outputElementContent(element, ow.getStream()); + ow.setMarkTo(); + ow.getStream().writeEndElement(); + ow.getStream().writeEndDocument(); + } catch (XMLStreamException e) { + throw new IllegalStateException(e); + } + ow.close(); + return ow.toString(); + } + + @Test + @Override // because omit still adds declaration.... + public void testOutputDocumentOmitDeclaration() { + Document doc = new Document(); + doc.addContent(new Element("root")); + FormatSetup setup = new FormatSetup() { + @Override + public void setup(Format fmt) { + fmt.setOmitDeclaration(true); + } + }; + String rtdec = "\n\n"; + checkOutput(doc, setup, + rtdec, + rtdec, + rtdec, + rtdec, + rtdec); + } + + @Test @Ignore + public void test_HighSurrogatePair() throws XMLStreamException, IOException, JDOMException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + ByteArrayOutputStream sw = new ByteArrayOutputStream(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(sw, "ISO-8859-1"); + outputter.output(doc, xsw); + String xml = sw.toString(); + assertEquals("" + format.getLineSeparator() + + "�� ��" + format.getLineSeparator(), xml); + } + + @Test @Ignore + public void test_HighSurrogatePairDecimal() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(baos, "ISO-8859-1"); + outputter.output(doc, xsw); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "�� ��" + format.getLineSeparator(), xml); + } + + @Test @Ignore + public void test_HighSurrogateAttPair() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(baos, "ISO-8859-1"); + outputter.output(doc, xsw); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "" + format.getLineSeparator(), xml); + } + + @Test @Ignore + public void test_HighSurrogateAttPairDecimal() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(baos, "ISO-8859-1"); + outputter.output(doc, xsw); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "" + format.getLineSeparator(), xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped + @Test @Ignore + public void test_RawSurrogatePair() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(baos, "ISO-8859-1"); + outputter.output(doc, xsw); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "��" + format.getLineSeparator(), xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped, when UTF-8 too + @Test + public void test_RawSurrogatePairUTF8() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("UTF-8"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + StringWriter baos = new StringWriter(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(baos); + outputter.output(doc, xsw); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "\uD800\uDC00" + format.getLineSeparator(), xml); + } + + // Construct illegal XML and check if the parser notices + @Test + public void test_ErrorSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + try { + doc.getRootElement().setText("\uD800\uDBFF"); + fail("Illegal surrogate pair should have thrown an exception"); + } + catch (IllegalDataException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + // Manually construct illegal XML and make sure the outputter notices + @Test + public void test_ErrorSurrogatePairOutput() throws JDOMException, IOException, XMLStreamException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Text t = new UncheckedJDOMFactory().text("\uD800\uDBFF"); + doc.getRootElement().setContent(t); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + StAXStreamOutputter outputter = new StAXStreamOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(baos); + try { + outputter.output(doc, xsw); + fail("Illegal surrogate pair output should have thrown an exception"); + } + catch (XMLStreamException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + + @Test + public void testStAXStreamOutputter() { + StAXStreamOutputter out = new StAXStreamOutputter(); + TestFormat.checkEquals(out.getFormat(), Format.getRawFormat()); + } + + + @Test + public void testStAXStreamOutputterFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + StAXStreamOutputter out = new StAXStreamOutputter(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + +// @Test +// public void testXMLOutputterXMLOutputter() { +// Format mine = Format.getCompactFormat(); +// StAXStreamProcessor xoutp = new StAXStreamOutputter().getStAXStream(); +// mine.setEncoding("US-ASCII"); +// // double-construct it. +// StAXStreamOutputter out = new StAXStreamOutputter(); +// TestFormat.checkEquals(mine, out.getFormat()); +// assertTrue(xoutp == out.getXMLOutputProcessor()); +// } + + @Test + public void testStAXStreamOutputterXMLOutputProcessor() { + StAXStreamProcessor xoutp = new AbstractStAXStreamProcessor() { + // nothing; + }; + // double-constrcut it. + StAXStreamOutputter out = new StAXStreamOutputter(xoutp); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + assertTrue(xoutp == out.getStAXStream()); + } + + @Test + public void testFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + // double-constcut it. + StAXStreamOutputter out = new StAXStreamOutputter(); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + out.setFormat(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + + @Test + public void testStAXStreamOutputProcessor() { + StAXStreamProcessor xoutp = new AbstractStAXStreamProcessor() { + // nothing; + }; + // double-constcut it. + StAXStreamOutputter out = new StAXStreamOutputter(); + StAXStreamProcessor xop = out.getStAXStream(); + out.setStAXStreamProcessor(xoutp); + assertTrue(xoutp != xop); + assertTrue(xoutp == out.getStAXStream()); + } + + @Test + public void testTrimFullWhite() throws XMLStreamException { + // See issue #31. + // https://github.com/hunterhacker/jdom/issues/31 + // This tests should pass when issue 31 is resolved. + Element root = new Element("root"); + root.addContent(new Text(" ")); + root.addContent(new Text("x")); + root.addContent(new Text(" ")); + Format mf = Format.getRawFormat(); + mf.setTextMode(TextMode.TRIM_FULL_WHITE); + StAXStreamOutputter xout = new StAXStreamOutputter(mf); + StringWriter sw = new StringWriter(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(sw); + xout.output(root, xsw); + assertEquals(" x ", sw.toString()); + } + + @Test + public void testClone() { + StAXStreamOutputter xo = new StAXStreamOutputter(); + assertTrue(xo != xo.clone()); + } + + @Test + public void testToString() { + Format fmt = Format.getCompactFormat(); + fmt.setLineSeparator("\n\t "); + StAXStreamOutputter out = new StAXStreamOutputter(fmt); + assertNotNull(out.toString()); + } + + /* + * The following are borrowed from the TestSAXOutputter + * The effect is that we compare the StAX string output with the re-parsed + * value of the input. + */ + + private void roundTripDocument(Document doc) { + roundTripDocument(doc, true); + roundTripDocument(doc, false); + } + + private void roundTripDocument(Document doc, boolean repairing) { + StAXStreamOutputter xout = new StAXStreamOutputter(Format.getRawFormat()); + // create a String representation of the input. + if (doc.hasRootElement()) { + normalizeAttributes(doc.getRootElement()); + } + + try { + StringWriter sw = new StringWriter(); + soutfactory.setProperty("javax.xml.stream.isRepairingNamespaces", repairing); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(sw); + xout.output(doc, xsw); + xsw.close(); + Document expect = new SAXBuilder().build(new StringReader(sw.toString())); + // convert the input to a SAX Stream + UnitTestUtil.compare(doc, expect); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + StringReader car = new StringReader(sw.toString()); + XMLStreamReader xsr = sinfactory.createXMLStreamReader(car); + + Document backagain = sbuilder.build(xsr); + xsr.close(); + + UnitTestUtil.compare(expect, backagain); + + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + } + + private void roundTripElement(Element emt) { + + try { + StAXStreamOutputter xout = new StAXStreamOutputter(Format.getRawFormat()); + + StringWriter sw = new StringWriter(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(sw); + xout.output(emt, xsw); + xsw.close(); + String expect = sw.toString(); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + XMLStreamReader xsr = sinfactory.createXMLStreamReader(new StringReader(expect)); + assertTrue(xsr.getEventType() == XMLStreamConstants.START_DOCUMENT); + assertTrue(xsr.hasNext()); + xsr.next(); + + Element backagain = (Element)sbuilder.fragment(xsr); + + // convert the input to a SAX Stream + + sw.getBuffer().setLength(0); + xsw = soutfactory.createXMLStreamWriter(sw); + xout.output(backagain, xsw); + xsw.close(); + + String actual = sw.toString(); + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + } + + private void roundTripFragment(List content) { + try { + StAXStreamOutputter xout = new StAXStreamOutputter(Format.getRawFormat()); + + StringWriter sw = new StringWriter(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(sw); + xout.output(content, xsw); + xsw.close(); + String expect = sw.toString(); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + XMLStreamReader xsr = sinfactory.createXMLStreamReader(new StringReader(expect)); +// assertTrue(xsr.getEventType() == XMLStreamConstants.START_DOCUMENT); +// assertTrue(xsr.hasNext()); +// xsr.next(); + + List backagain = sbuilder.buildFragments(xsr, new DefaultStAXFilter()); + + // convert the input to a SAX Stream + + sw.getBuffer().setLength(0); + xsw = soutfactory.createXMLStreamWriter(sw); + xout.output(backagain, xsw); + xsw.close(); + + String actual = sw.toString(); + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + + } + + private void roundTripFragment(Content content) { + try { + StAXStreamOutputter xout = new StAXStreamOutputter(Format.getRawFormat()); + + StringWriter sw = new StringWriter(); + XMLStreamWriter xsw = soutfactory.createXMLStreamWriter(sw); + switch(content.getCType()) { + case CDATA : + xout.output((CDATA)content, xsw); + break; + case Text: + xout.output((Text)content, xsw); + break; + case Comment: + xout.output((Comment)content, xsw); + break; + case DocType: + xout.output((DocType)content, xsw); + break; + case Element: + xout.output((Element)content, xsw); + break; + case EntityRef: + xout.output((EntityRef)content, xsw); + break; + case ProcessingInstruction: + xout.output((ProcessingInstruction)content, xsw); + break; + default: + throw new IllegalStateException(content.getCType().toString()); + } + xsw.close(); + String expect = sw.toString(); + + StAXStreamBuilder sbuilder = new StAXStreamBuilder(); + + Content backagain = sbuilder.fragment( + sinfactory.createXMLStreamReader(new StringReader(expect))); + + // convert the input to a SAX Stream + + sw.getBuffer().setLength(0); + xsw = soutfactory.createXMLStreamWriter(sw); + switch(content.getCType()) { + case CDATA : + xout.output((CDATA)backagain, xsw); + break; + case Text: + xout.output((Text)backagain, xsw); + break; + case Comment: + xout.output((Comment)backagain, xsw); + break; + case DocType: + xout.output((DocType)backagain, xsw); + break; + case Element: + xout.output((Element)backagain, xsw); + break; + case EntityRef: + xout.output((EntityRef)backagain, xsw); + break; + case ProcessingInstruction: + xout.output((ProcessingInstruction)backagain, xsw); + break; + default: + throw new IllegalStateException(backagain.getCType().toString()); + } + xsw.close(); + + String actual = sw.toString(); + assertEquals(expect, actual); + } catch (Exception e) { + failException("Failed to round-trip the document with exception: " + + e.getMessage(), e); + } + } + + @Test + public void testRTOutputDocumentSimple() { + Document doc = new Document(new Element("root")); + roundTripDocument(doc); + } + + @Test + public void testRTOutputDocumentDefaultNS() { + Element root = new Element("root", "http://jdom/noprefix"); + Document doc = new Document(root); + roundTripDocument(doc); + } + + @Test + @Ignore + // TODO + public void testRTOutputDocumentFull() { + Document doc = new Document(); + DocType dt = new DocType("root"); + dt.setInternalSubset(" "); + doc.addContent(dt); + doc.addContent(new Comment("This is a document")); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + Element e = new Element("root"); + e.addContent(new EntityRef("ref")); + doc.addContent(e); + roundTripDocument(doc); + } + + @Test + public void testOutputDocumentRootAttNS() { + Document doc = new Document(); + Element e = new Element("root"); + e.setAttribute(new Attribute("att", "val", Namespace.getNamespace("ans", "mynamespace"))); + doc.addContent(e); + roundTripDocument(doc); + } + + @Test + public void testOutputDocumentAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + Document doc = new Document(emt); + roundTripDocument(doc); + } + + @Test + public void testOutputDocumentNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addContent(new Element("child", Namespace.getNamespace("", "childuri"))); + Document doc = new Document(emt); + roundTripDocument(doc); + } + + @Test + public void testRTOutputList() { + List list = new ArrayList(); + list.add(new ProcessingInstruction("jdomtest", "")); + list.add(new Comment("comment")); + list.add(new Element("root")); + roundTripFragment(list); + } + + @Test + public void testOutputElementAttributes() { + Element emt = new Element("root"); + emt.setAttribute("att", "val"); + emt.setAttribute(new Attribute("attx", "valx", AttributeType.UNDECLARED)); + roundTripElement(emt); + } + + @Test + public void testRTOutputElementNamespaces() { + Element emt = new Element("root", Namespace.getNamespace("ns", "myns")); + Namespace ans = Namespace.getNamespace("ans", "attributens"); + emt.addNamespaceDeclaration(ans); + emt.addNamespaceDeclaration(Namespace.getNamespace("two", "two")); + emt.setAttribute(new Attribute("att", "val", ans)); + emt.addContent(new Element("child", Namespace.getNamespace("", "childns"))); + roundTripElement(emt); + } + + @Test + @Ignore + // TODO + public void testOutputFragmentList() { + List list = new ArrayList(); + list.add(new ProcessingInstruction("jdomtest", "")); + list.add(new Comment("comment")); + list.add(new CDATA("foo")); + list.add(new Element("root")); + list.add(new Text("bar")); + roundTripFragment(list); + } + + @Test + @Ignore + // TODO + public void testOutputFragmentContent() { + roundTripFragment(new ProcessingInstruction("jdomtest", "")); + roundTripFragment(new Comment("comment")); + roundTripFragment(new CDATA("foo")); + roundTripFragment(new Element("root")); + roundTripFragment(new Text("bar")); + } + + @Test + public void testOutputNullContent() throws JDOMException { + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void startDocument() throws SAXException { + throw new SAXException("SHould not be reaching this, ever"); + } + }; + SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler); + + Document doc = null; + Element emt = null; + List list = null; + List empty = new ArrayList(); + saxout.output(doc); + saxout.output(emt); + saxout.output(list); + saxout.output(empty); + saxout.outputFragment(emt); + saxout.outputFragment(list); + saxout.outputFragment(empty); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestXMLOutputProcessor.java b/test/src/java/org/jdom/test/cases/output/TestXMLOutputProcessor.java new file mode 100644 index 0000000..77bd4e7 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestXMLOutputProcessor.java @@ -0,0 +1,387 @@ +package org.jdom.test.cases.output; + +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.Writer; + +import org.jdom.IllegalDataException; +import org.jdom.Text; +import org.jdom.output.Format; +import org.jdom.output.support.AbstractXMLOutputProcessor; +import org.jdom.output.support.FormatStack; + +import org.junit.Test; + +/** + * + * @author Rolf Lear + * + */ +@SuppressWarnings("javadoc") +public class TestXMLOutputProcessor extends AbstractXMLOutputProcessor { + + private static final String formatChar(char ch) { + return String.format(" char '%s' (0x%04x)", "" + ch, (int)ch); + } + + private static final class CheckWriter extends Writer { + private final char[] expect; + private int cursor = 0; + private CheckWriter(String expect) { + this.expect = expect.toCharArray(); + } + + private final String formatLast() { + if (cursor < 10) { + return "\"" + new String(expect, 0, cursor) + "\""; + } + return "\"...{x" + (cursor - 10) + "}" + new String(expect, cursor - 10, 10) + "\""; + } + + private void checkChar(char ch) { + if (cursor >= expect.length) { + fail("We have additional characters. Not expecting " + + formatChar(ch) + " after " + formatLast()); + } + if (ch != expect[cursor]) { + fail("Expecting " + formatChar(expect[cursor]) + " not " + + formatChar(ch) + " after " + formatLast()); + } + cursor++; + } + + @Override + public void write(char[] cbuf, int off, int len) { + for (int i = 0; i < len; i++) { + checkChar(cbuf[off + i]); + } + + } + @Override + public void write(int c) { + checkChar((char)c); + } + + @Override + public void close() { + flush(); + } + + @Override + public void flush() { + // this is called after a complete output. + if (cursor < expect.length) { + fail ("Expected additional characters after " + formatLast()); + } + } + + } + + private final Format RAW; + private final FormatStack fsraw; + + public TestXMLOutputProcessor() { + RAW = Format.getRawFormat(); + RAW.setEncoding("US-ASCII"); + fsraw = new FormatStack(RAW); + } + + + @Test + public void testAttributeEscapedEntitiesFilter() throws IOException { + CheckWriter cw = new CheckWriter(" " œ ' & < > 𐀀 "); + attributeEscapedEntitiesFilter(cw, fsraw, " \" \u0153 ' & < > \r \n \t \uD800\uDC00 "); + cw.close(); + } + + @Test + public void testAttributeEscapedEntitiesFilterASCII() throws IOException { + CheckWriter cw = new CheckWriter(" " ' & < > 𐀀 "); + attributeEscapedEntitiesFilter(cw, fsraw, " \" ' & < > \r \n \t \uD800\uDC00 "); + cw.close(); + } + + @Test + public void testAttributeEscapedEntitiesFilterErrorMid() { + CheckWriter cw = new CheckWriter(" " ' & < > 𐀀 "); + try { + attributeEscapedEntitiesFilter(cw, fsraw, " \" ' & < > \r \n \t \uD800 \uDC00 "); + fail("Should have missed the low surrogate..."); + } catch (IllegalDataException ide) { + //good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalDataException but got " + e.getClass()); + } + } + + @Test + public void testAttributeEscapedEntitiesFilterErrorEnd() { + CheckWriter cw = new CheckWriter(" " ' & < > 𐀀 "); + try { + attributeEscapedEntitiesFilter(cw, fsraw, " \" ' & < > \r \n \t \uD800"); + fail("Should have missed the low surrogate..."); + } catch (IllegalDataException ide) { + //good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalDataException but got " + e.getClass()); + } + } + + @Test + public void testAttributeEscapedEntitiesFilterNoEscape() throws IOException { + CheckWriter cw = new CheckWriter(" \" ' & < > \r \n \t \uD800\uDC00 "); + FormatStack tmps = new FormatStack(RAW); + tmps.setEscapeOutput(false); + attributeEscapedEntitiesFilter(cw, tmps, " \" ' & < > \r \n \t \uD800\uDC00 "); + cw.close(); + } + + @Test + public void testTextRawWriterString() throws IOException { + CheckWriter cw = new CheckWriter(" \" ' & < > \r \n \t \uD800\uDC00 "); + textRaw(cw, " \" ' & < > \r \n \t \uD800\uDC00 "); + cw.close(); + } + + @Test + public void testTextRawWriterChar() throws IOException { + CheckWriter cw = new CheckWriter(" "); + textRaw(cw, ' '); + cw.close(); + } + + @Test + public void testTextEntityRef() throws IOException { + CheckWriter cw = new CheckWriter("&er;"); + textEntityRef(cw, "er"); + cw.close(); + } + + @Test + public void testTextCDATARaw() throws IOException { + CheckWriter cw = new CheckWriter(" \r \n \t \uD800\uDC00 ]]>"); + textCDATA(cw, " \" ' & < > \r \n \t \uD800\uDC00 "); + cw.close(); + } + + @Test + public void testTextCDATARawEmpty() throws IOException { + CheckWriter cw = new CheckWriter(""); + textCDATA(cw, ""); + cw.close(); + } + + @Test + public void testTextEscapedEntitiesFilter() throws IOException { + CheckWriter cw = new CheckWriter(" \" œ ' & < > \r\n \t 𐀀 "); + String data = " \" \u0153 ' & < > \r \n \t \uD800\uDC00 "; + printText(cw, fsraw, new Text(data)); + cw.close(); + } + + @Test + public void testTextEscapedEntitiesFilterErrorMid() { + CheckWriter cw = new CheckWriter(" \" ' & < > \r\n \t 𐀀 "); + // the HighSurrogate is broken here.... + try { + String data = " \" ' & < > \r \n \t \uD800 \uDC00 "; + printText(cw, fsraw, new Text(data)); + fail("Should have missed the low surrogate..."); + } catch (IllegalDataException ide) { + //good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalDataException but got " + e.getClass()); + } + } + + @Test + public void testTextEscapedEntitiesFilterErrorEnd() { + CheckWriter cw = new CheckWriter(" \" ' & < > \r\n \t 𐀀 "); + // the HighSurrogate is broken here.... + try { + String data = " \" ' & < > \r \n \t \uD800"; + printText(cw, fsraw, new Text(data)); + fail("Should have missed the low surrogate..."); + } catch (IllegalDataException ide) { + //good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalDataException but got " + e.getClass()); + } + } + + @Test + public void testTextEscapedEntitiesFilterNoEscape() throws IOException { + CheckWriter cw = new CheckWriter(" \" ' & < > \r \n \t \uD800\uDC00 "); + String data = " \" ' & < > \r \n \t \uD800\uDC00 "; + FormatStack tmps = new FormatStack(RAW); + tmps.setEscapeOutput(false); + printText(cw, tmps, new Text(data)); + cw.close(); + } + + @Test + public void testTextEscapeRaw() throws IOException { + CheckWriter cw = new CheckWriter(" \" ' & < > \r\n \t 𐀀 "); + String s = " \" ' & < > \r \n \t \uD800\uDC00 "; + printText(cw, fsraw, new Text(s)); + cw.close(); + } + + @Test + public void testTextEscapeRawEmpty() throws IOException { + CheckWriter cw = new CheckWriter(""); + printText(cw, fsraw, new Text("")); + cw.close(); + } + + +// @Test +// public void testProcessWriterFormatDocument() { +// +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatDocType() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatElement() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatListOfQextendsContent() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatCDATA() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatText() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatComment() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatProcessingInstruction() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testProcessWriterFormatEntityRef() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testWriteWriterString() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testWriteWriterChar() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintDocument() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintDeclaration() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintDocType() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintProcessingInstruction() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintComment() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintEntityRef() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintCDATA() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintText() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintElement() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintContent() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintTextConsecutive() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testHelperContentDispatcher() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testHelperTextType() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testHelperRawTextType() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintNamespace() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testPrintAttribute() { +// fail("Not yet implemented"); +// } +// +// @Test +// public void testIsAllWhitespace() { +// fail("Not yet implemented"); +// } +// +// +// + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestXMLOutputter.java b/test/src/java/org/jdom/test/cases/output/TestXMLOutputter.java new file mode 100644 index 0000000..40c055e --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestXMLOutputter.java @@ -0,0 +1,551 @@ +package org.jdom.test.cases.output; + +/* Please run replic.pl on me ! */ +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ + +import org.jdom.*; +import org.jdom.input.SAXBuilder; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.LineSeparator; +import org.jdom.output.XMLOutputter; +import org.jdom.output.support.AbstractXMLOutputProcessor; +import org.jdom.output.support.XMLOutputProcessor; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import javax.xml.transform.Result; +import java.io.*; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; + +@SuppressWarnings("javadoc") +public final class TestXMLOutputter extends AbstractTestOutputter { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestXMLOutputter.class); + } + + public TestXMLOutputter() { + super(true, true, false, false, false); + } + + private XMLOutputter getOutputter(Format format) { + return new XMLOutputter(format); + } + + @Override + public String outputDocumentAsString(Format format, Document doc) { + return getOutputter(format).outputString(doc); + } + + @Override + public String outputDocTypeAsString(Format format, DocType doctype) { + return getOutputter(format).outputString(doctype); + } + + @Override + public String outputElementAsString(Format format, Element element) { + return getOutputter(format).outputString(element); + } + + @Override + public String outputListAsString(Format format, List list) { + return getOutputter(format).outputString(list); + } + + @Override + public String outputCDataAsString(Format format, CDATA cdata) { + return getOutputter(format).outputString(cdata); + } + + @Override + public String outputTextAsString(Format format, Text text) { + return getOutputter(format).outputString(text); + } + + @Override + public String outputCommentAsString(Format format, Comment comment) { + return getOutputter(format).outputString(comment); + } + + @Override + public String outputPIAsString(Format format, ProcessingInstruction pi) { + return getOutputter(format).outputString(pi); + } + + @Override + public String outputEntityRefAsString(Format format, EntityRef entity) { + return getOutputter(format).outputString(entity); + } + + @Override + public String outputElementContentString(Format format, Element element) { + StringWriter out = new StringWriter(); + try { + getOutputter(format).outputElementContent(element, out); // output() flushes + } catch (IOException e) { + // swallow - will never happen. + } + return out.toString(); + } + + @Test @Ignore + @Override + public void testTextWhitespace() { + } + + @Test @Ignore + @Override + public void testCDATAEmpty() { + } + + @Test @Ignore + @Override + public void testCDATAWhitespace() { + } + + @Test @Ignore + @Override + public void testMultiText() { + } + + @Test @Ignore + @Override + public void testDocumentSimple() { + } + + @Test @Ignore + @Override + public void testDocumentDocType() { + } + + @Test @Ignore + @Override + public void testDocumentComment() { + } + + @Test @Ignore + @Override + public void testOutputElementAttributeNotSpecifiedA() { + } + + @Test @Ignore + @Override + public void testOutputElementAttributeNotSpecifiedB() { + } + + @Test @Ignore + @Override + public void testOutputElementPreserveSpaceComplex() { + } + + @Test @Ignore + @Override + public void testOutputElementMultiText() { + } + + @Test @Ignore + @Override + public void testOutputElementMultiMostWhiteExpandEmpty() { + } + + @Test @Ignore + @Override + public void testOutputElementMixedMultiCDATA() { + } + + @Test @Ignore + @Override + public void testOutputElementMixedMultiEntityRef() { + } + + @Test @Ignore + @Override + public void testOutputElementMixedMultiText() { + } + + @Test @Ignore + @Override + public void testOutputElementMixedMultiZeroText() { + } + + @Test @Ignore + @Override + public void testOutputElementInterleavedEmptyText() { + } + + @Test @Ignore + @Override + public void testOutputElementMultiEntityLeftRight() { + } + + @Test @Ignore + @Override + public void testOutputElementMultiTrimLeftRight() { + } + + @Test @Ignore + @Override + public void testOutputElementMultiCDATALeftRight() { + } + + @Test @Ignore + @Override + public void testOutputElementNamespaces() { + } + + @Test @Ignore + @Override + public void testOutputDocumentSimple() { + } + + @Test @Ignore + @Override + public void testOutputDocumentOmitEncoding() { + } + + @Test @Ignore + @Override + public void testOutputDocumentOmitDeclaration() { + } + + @Test @Ignore + @Override + public void testOutputDocumentFull() { + } + + @Test @Ignore + @Override + public void testDeepNesting() { + } + + @Test @Ignore + @Override + public void testOutputEscapedMixedMultiText() { + } + + + @Test + public void test_HighSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter outputter = new XMLOutputter(format); + StringWriter sw = new StringWriter(); + outputter.output(doc, sw); + String xml = sw.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀 𐀀" + format.getLineSeparator(), xml); + } + + @Test + public void test_HighSurrogatePairDecimal() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter outputter = new XMLOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀 𐀀" + format.getLineSeparator(), xml); + } + + @Test + public void test_HighSurrogateAttPair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter outputter = new XMLOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "" + format.getLineSeparator(), xml); + } + + @Test + public void test_HighSurrogateAttPairDecimal() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter outputter = new XMLOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "" + format.getLineSeparator(), xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped + @Test + public void test_RawSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter outputter = new XMLOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀" + format.getLineSeparator(), xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped, when UTF-8 too + @Test + public void test_RawSurrogatePairUTF8() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("UTF-8"); + XMLOutputter outputter = new XMLOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀" + format.getLineSeparator(), xml); + } + + // Construct illegal XML and check if the parser notices + @Test + public void test_ErrorSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + try { + doc.getRootElement().setText("\uD800\uDBFF"); + fail("Illegal surrogate pair should have thrown an exception"); + } + catch (IllegalDataException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + // Manually construct illegal XML and make sure the outputter notices + @Test + public void test_ErrorSurrogatePairOutput() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Text t = new UncheckedJDOMFactory().text("\uD800\uDBFF"); + doc.getRootElement().setContent(t); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter outputter = new XMLOutputter(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + outputter.output(doc, baos); + fail("Illegal surrogate pair output should have thrown an exception"); + } + catch (IllegalDataException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + + @Test + public void testXMLOutputter() { + XMLOutputter out = new XMLOutputter(); + TestFormat.checkEquals(out.getFormat(), Format.getRawFormat()); + } + + + @Test + public void testXMLOutputterFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + XMLOutputter out = new XMLOutputter(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + + @Test + public void testFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + // double-constcut it. + XMLOutputter out = new XMLOutputter(); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + out.setFormat(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + + @Test + public void testNoLineSeparator() { + XMLOutputter out = new XMLOutputter(Format.getRawFormat().setLineSeparator(LineSeparator.NONE)); + assertEquals("", + out.outputString(new Document(new Element("root")))); + } + + @Test + public void testEscapeAttributeEntities() { + Map totest = new LinkedHashMap(); + + totest.put("\"", """); + totest.put("&", "&"); + totest.put("<", "<"); + totest.put(">", ">"); + totest.put("\t", " "); + totest.put("\r", " "); + totest.put("\n", " "); + totest.put("\nz\n\n", " z "); + + totest.put("'", "'"); + + totest.put("Frodo's Journey", "Frodo's Journey"); + + + XMLOutputter out = new XMLOutputter(); + + for (Map.Entry me : totest.entrySet()) { + if (!me.getValue().equals(out.escapeAttributeEntities(me.getKey()))) { + assertEquals("Failed attempt to escape '" + me.getKey() + "'", + me.getValue(), out.escapeAttributeEntities(me.getKey())); + } + } + } + + @Test + public void testEscapeElementEntities() { + Map totest = new LinkedHashMap(); + + totest.put("\"", "\""); + totest.put("&", "&"); + totest.put("<", "<"); + totest.put(">", ">"); + totest.put("> >>", "> >>"); + totest.put("\t", "\t"); + totest.put("\r", " "); + totest.put("\n", "\r\n"); + + totest.put("'", "'"); + + totest.put("Frodo's Journey", "Frodo's Journey"); + + + XMLOutputter out = new XMLOutputter(); + + for (Map.Entry me : totest.entrySet()) { + if (!me.getValue().equals(out.escapeElementEntities(me.getKey()))) { + assertEquals("Failed attempt to escape '" + me.getKey() + "'", + me.getValue(), out.escapeElementEntities(me.getKey())); + } + } + } + + + + @Test + public void testClone() { + XMLOutputter xo = new XMLOutputter(); + assertTrue(xo != xo.clone()); + } + + @Test + public void testToString() { + Format fmt = Format.getCompactFormat(); + fmt.setLineSeparator("\n\t "); + XMLOutputter out = new XMLOutputter(fmt); + assertNotNull(out.toString()); + } + + /** + * The following method will run the output data through each of the three base + * formatters, raw, compact, and pretty. It will also run each of those + * formatters as the outputString(content), output(content, OutputStream) + * and output(content, Writer). + * + * The expectation is that the results of the three output forms (String, + * OutputStream, and Writer) will be identical, and that it will match + * the expected value for the appropriate formatter. + * + * @param content The content to output + * @param methodprefix What the methods are called + * @param clazz The class used as the parameter for the methods. + * @param setup A callback mechanism to modify the formatters + * @param raw What we expect the content to look like with the RAW format + * @param compact What we expect the content to look like with the COMPACT format + * @param pretty What we expect the content to look like with the PRETTY format + * @param trimfw What we expect the content to look like with the TRIM_FULL_WHITE format + */ + @Override + protected void checkOutput(Object content, String methodprefix, Class clazz, + FormatSetup setup, String raw, String compact, String pretty, String tso, String trimfw) { + + super.checkOutput(content, methodprefix, clazz, setup, raw, compact, pretty, tso, trimfw); + + Method mstring = getMethod(methodprefix + "String", clazz); + Method mstream = getMethod(methodprefix, clazz, OutputStream.class); + Method mwriter = getMethod(methodprefix, clazz, Writer.class); + + String[] descn = new String[] {"Raw", "Compact", "Pretty", "TrimFullWhite"}; + Format ftrimfw = Format.getPrettyFormat(); + ftrimfw.setTextMode(TextMode.TRIM_FULL_WHITE); + Format[] formats = new Format[] { + getFormat(setup, Format.getRawFormat()), + getFormat(setup, Format.getCompactFormat()), + getFormat(setup, Format.getPrettyFormat()), + getFormat(setup, ftrimfw)}; + String[] result = new String[] {expect(raw), expect(compact), expect(pretty), expect(trimfw)}; + + for (int i = 0; i < 4; i++) { + XMLOutputter out = new XMLOutputter(formats[i]); + ByteArrayOutputStream baos = new ByteArrayOutputStream(result[i].length() * 2); + CharArrayWriter caw = new CharArrayWriter(result[i].length() + 2); + try { + if (mstring != null) { + String rstring = (String) mstring.invoke(out, content); + assertEquals("outputString Format " + descn[i], result[i], rstring); + } + if (mstream != null) { + mstream.invoke(out, content, baos); + String rstream = new String(baos.toByteArray()); + assertEquals("output OutputStream Format " + descn[i], result[i], rstream); + } + if (mwriter != null) { + mwriter.invoke(out, content, caw); + String rwriter = String.valueOf(caw.toCharArray()); + assertEquals("output Writer Format " + descn[i], result[i], rwriter); + } + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to process " + descn[i] + " on content " + clazz + ": " + e.getMessage()); + } + } + } + + private Method getMethod(String name, Class...classes) { + try { + return XMLOutputter.class.getMethod(name, classes); + } catch (Exception e) { + // ignore. + System.out.println("Can't find " + name + " on " + this.getClass().getName() + ": " + e.getMessage()); + } + return null; + } + + +} diff --git a/test/src/java/org/jdom/test/cases/output/TestXMLOutputter2.java b/test/src/java/org/jdom/test/cases/output/TestXMLOutputter2.java new file mode 100644 index 0000000..c6089a6 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/output/TestXMLOutputter2.java @@ -0,0 +1,519 @@ +package org.jdom.test.cases.output; + +/* Please run replic.pl on me ! */ +/** + * Please put a description of your test here. + * + * @author unascribed + * @version 0.1 + */ +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayOutputStream; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Method; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.transform.Result; + +import org.junit.Test; +import org.junit.runner.JUnitCore; + +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.IllegalDataException; +import org.jdom.JDOMException; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.UncheckedJDOMFactory; +import org.jdom.input.SAXBuilder; +import org.jdom.output.Format; +import org.jdom.output.Format.TextMode; +import org.jdom.output.support.AbstractXMLOutputProcessor; +import org.jdom.output.support.XMLOutputProcessor; +import org.jdom.output.XMLOutputter2; + +@SuppressWarnings("javadoc") +public final class TestXMLOutputter2 extends AbstractTestOutputter { + + /** + * The main method runs all the tests in the text ui + */ + public static void main (String args[]) + { + JUnitCore.runClasses(TestXMLOutputter2.class); + } + + public TestXMLOutputter2() { + super(true, true, false, false, false); + } + + private XMLOutputter2 getOutputter(Format format) { + return new XMLOutputter2(format); + } + + @Override + public String outputDocumentAsString(Format format, Document doc) { + return getOutputter(format).outputString(doc); + } + + @Override + public String outputDocTypeAsString(Format format, DocType doctype) { + return getOutputter(format).outputString(doctype); + } + + @Override + public String outputElementAsString(Format format, Element element) { + return getOutputter(format).outputString(element); + } + + @Override + public String outputListAsString(Format format, List list) { + return getOutputter(format).outputString(list); + } + + @Override + public String outputCDataAsString(Format format, CDATA cdata) { + return getOutputter(format).outputString(cdata); + } + + @Override + public String outputTextAsString(Format format, Text text) { + return getOutputter(format).outputString(text); + } + + @Override + public String outputCommentAsString(Format format, Comment comment) { + return getOutputter(format).outputString(comment); + } + + @Override + public String outputPIAsString(Format format, ProcessingInstruction pi) { + return getOutputter(format).outputString(pi); + } + + @Override + public String outputEntityRefAsString(Format format, EntityRef entity) { + return getOutputter(format).outputString(entity); + } + + @Override + public String outputElementContentString(Format format, Element element) { + return getOutputter(format).outputElementContentString(element); + } + + + + + @Test + public void test_HighSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter2 outputter = new XMLOutputter2(format); + StringWriter sw = new StringWriter(); + outputter.output(doc, sw); + String xml = sw.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀 𐀀" + format.getLineSeparator(), xml); + } + + @Test + public void test_HighSurrogatePairDecimal() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("𐀀 𐀀")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter2 outputter = new XMLOutputter2(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀 𐀀" + format.getLineSeparator(), xml); + } + + @Test + public void test_HighSurrogateAttPair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter2 outputter = new XMLOutputter2(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "" + format.getLineSeparator(), xml); + } + + @Test + public void test_HighSurrogateAttPairDecimal() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter2 outputter = new XMLOutputter2(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "" + format.getLineSeparator(), xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped + @Test + public void test_RawSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter2 outputter = new XMLOutputter2(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀" + format.getLineSeparator(), xml); + } + + // Construct a raw surrogate pair character and confirm it outputs hex escaped, when UTF-8 too + @Test + public void test_RawSurrogatePairUTF8() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("\uD800\uDC00")); + Format format = Format.getCompactFormat().setEncoding("UTF-8"); + XMLOutputter2 outputter = new XMLOutputter2(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + outputter.output(doc, baos); + String xml = baos.toString(); + assertEquals("" + format.getLineSeparator() + + "𐀀" + format.getLineSeparator(), xml); + } + + // Construct illegal XML and check if the parser notices + @Test + public void test_ErrorSurrogatePair() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + try { + doc.getRootElement().setText("\uD800\uDBFF"); + fail("Illegal surrogate pair should have thrown an exception"); + } + catch (IllegalDataException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + // Manually construct illegal XML and make sure the outputter notices + @Test + public void test_ErrorSurrogatePairOutput() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + builder.setExpandEntities(true); + Document doc = builder.build(new StringReader("")); + Text t = new UncheckedJDOMFactory().text("\uD800\uDBFF"); + doc.getRootElement().setContent(t); + Format format = Format.getCompactFormat().setEncoding("ISO-8859-1"); + XMLOutputter2 outputter = new XMLOutputter2(format); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + outputter.output(doc, baos); + fail("Illegal surrogate pair output should have thrown an exception"); + } + catch (IllegalDataException e) { + // do nothing + } catch (Exception e) { + fail ("Unexpected exception " + e.getClass()); + } + } + + + @Test + public void testXMLOutputter() { + XMLOutputter2 out = new XMLOutputter2(); + TestFormat.checkEquals(out.getFormat(), Format.getRawFormat()); + } + + + @Test + public void testXMLOutputterFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + XMLOutputter2 out = new XMLOutputter2(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + + @Test + public void testXMLOutputterXMLOutputter() { + Format mine = Format.getCompactFormat(); + XMLOutputProcessor xoutp = new XMLOutputter2().getXMLOutputProcessor(); + mine.setEncoding("US-ASCII"); + // double-constcut it. + XMLOutputter2 out = new XMLOutputter2(new XMLOutputter2(mine)); + TestFormat.checkEquals(mine, out.getFormat()); + assertTrue(xoutp == out.getXMLOutputProcessor()); + } + + @Test + public void testXMLOutputterXMLOutputProcessor() { + XMLOutputProcessor xoutp = new AbstractXMLOutputProcessor() { + // nothing; + }; + // double-constrcut it. + XMLOutputter2 out = new XMLOutputter2(xoutp); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + assertTrue(xoutp == out.getXMLOutputProcessor()); + } + + @Test + public void testFormat() { + Format mine = Format.getCompactFormat(); + mine.setEncoding("US-ASCII"); + // double-constcut it. + XMLOutputter2 out = new XMLOutputter2(); + TestFormat.checkEquals(Format.getRawFormat(), out.getFormat()); + out.setFormat(mine); + TestFormat.checkEquals(mine, out.getFormat()); + } + + @Test + public void testXMLOutputProcessor() { + XMLOutputProcessor xoutp = new AbstractXMLOutputProcessor() { + // nothing; + }; + // double-constcut it. + XMLOutputter2 out = new XMLOutputter2(); + XMLOutputProcessor xop = out.getXMLOutputProcessor(); + out.setXMLOutputProcessor(xoutp); + assertTrue(xoutp != xop); + assertTrue(xoutp == out.getXMLOutputProcessor()); + } + + @Test + public void testEscapeAttributeEntities() { + Map totest = new LinkedHashMap(); + + totest.put("\"", """); + totest.put("&", "&"); + totest.put("<", "<"); + totest.put(">", ">"); + totest.put("\t", " "); + totest.put("\r", " "); + totest.put("\n", " "); + totest.put("\nz\n\n", " z "); + + totest.put("'", "'"); + + totest.put("Frodo's Journey", "Frodo's Journey"); + + + XMLOutputter2 out = new XMLOutputter2(); + + for (Map.Entry me : totest.entrySet()) { + if (!me.getValue().equals(out.escapeAttributeEntities(me.getKey()))) { + assertEquals("Failed attempt to escape '" + me.getKey() + "'", + me.getValue(), out.escapeAttributeEntities(me.getKey())); + } + } + } + + @Test + public void testEscapeElementEntities() { + Map totest = new LinkedHashMap(); + + totest.put("\"", "\""); + totest.put("&", "&"); + totest.put("<", "<"); + totest.put(">", ">"); + totest.put("> >>", "> >>"); + totest.put("\t", "\t"); + totest.put("\r", " "); + totest.put("\n", "\r\n"); + + totest.put("'", "'"); + + totest.put("Frodo's Journey", "Frodo's Journey"); + + + XMLOutputter2 out = new XMLOutputter2(); + + for (Map.Entry me : totest.entrySet()) { + if (!me.getValue().equals(out.escapeElementEntities(me.getKey()))) { + assertEquals("Failed attempt to escape '" + me.getKey() + "'", + me.getValue(), out.escapeElementEntities(me.getKey())); + } + } + } + + + + @Test + public void testTrimFullWhite() { + // See issue #31. + // https://github.com/hunterhacker/jdom/issues/31 + // This tests should pass when issue 31 is resolved. + Element root = new Element("root"); + root.addContent(new Text(" ")); + root.addContent(new Text("x")); + root.addContent(new Text(" ")); + Format mf = Format.getRawFormat(); + mf.setTextMode(TextMode.TRIM_FULL_WHITE); + XMLOutputter2 xout = new XMLOutputter2(mf); + String output = xout.outputString(root); + assertEquals(" x ", output); + } + + + @Test + public void testOutputElementIgnoreTrAXEscapingPIs() { + Element root = new Element("root"); + root.addContent(new Text("&")); + root.addContent(new ProcessingInstruction(Result.PI_DISABLE_OUTPUT_ESCAPING, "")); + root.addContent(new Text(" && ")); + root.addContent(new ProcessingInstruction(Result.PI_ENABLE_OUTPUT_ESCAPING, "")); + root.addContent(new Text("&")); + String expect = "& && &"; + String excompact = "&&&&"; + String expretty = "\n &\n &&\n &\n"; + String extfw = "\n &\n && \n &\n"; + checkOutput(root, + expect, + excompact, + expretty, + expretty, + extfw); + } + + + @Test + public void testClone() { + XMLOutputter2 xo = new XMLOutputter2(); + assertTrue(xo != xo.clone()); + } + + @Test + public void testToString() { + Format fmt = Format.getCompactFormat(); + fmt.setLineSeparator("\n\t "); + XMLOutputter2 out = new XMLOutputter2(fmt); + assertNotNull(out.toString()); + } + + @Test + public void testCRNLEscaping() { + Document doc = new Document(); + Element root = new Element("root"); + Element child1 = new Element("child1"); + Element child2 = new Element("child2"); + Text stuff = new Text("foo"); + root.addContent(child1); + root.addContent(stuff); + root.addContent(child2); + doc.setRootElement(root); + XMLOutputter2 xout = new XMLOutputter2(Format.getPrettyFormat()); + String actual = xout.outputString(doc); + String expect = "\r\n" + + "\r\n" + + " \r\n" + + " foo\r\n" + + " \r\n" + + "\r\n"; + assertEquals(expect, actual); + } + + /** + * The following method will run the output data through each of the three base + * formatters, raw, compact, and pretty. It will also run each of those + * formatters as the outputString(content), output(content, OutputStream) + * and output(content, Writer). + * + * The expectation is that the results of the three output forms (String, + * OutputStream, and Writer) will be identical, and that it will match + * the expected value for the appropriate formatter. + * + * @param content The content to output + * @param methodprefix What the methods are called + * @param clazz The class used as the parameter for the methods. + * @param setup A callback mechanism to modify the formatters + * @param raw What we expect the content to look like with the RAW format + * @param compact What we expect the content to look like with the COMPACT format + * @param pretty What we expect the content to look like with the PRETTY format + * @param trimfw What we expect the content to look like with the TRIM_FULL_WHITE format + */ + @Override + protected void checkOutput(Object content, String methodprefix, Class clazz, + FormatSetup setup, String raw, String compact, String pretty, String tso, String trimfw) { + + super.checkOutput(content, methodprefix, clazz, setup, raw, compact, pretty, tso, trimfw); + + Method mstring = getMethod(methodprefix + "String", clazz); + Method mstream = getMethod(methodprefix, clazz, OutputStream.class); + Method mwriter = getMethod(methodprefix, clazz, Writer.class); + + String[] descn = new String[] {"Raw", "Compact", "Pretty", "TrimFullWhite"}; + Format ftrimfw = Format.getPrettyFormat(); + ftrimfw.setTextMode(TextMode.TRIM_FULL_WHITE); + Format[] formats = new Format[] { + getFormat(setup, Format.getRawFormat()), + getFormat(setup, Format.getCompactFormat()), + getFormat(setup, Format.getPrettyFormat()), + getFormat(setup, ftrimfw)}; + String[] result = new String[] {expect(raw), expect(compact), expect(pretty), expect(trimfw)}; + + for (int i = 0; i < 4; i++) { + XMLOutputter2 out = new XMLOutputter2(formats[i]); + ByteArrayOutputStream baos = new ByteArrayOutputStream(result[i].length() * 2); + CharArrayWriter caw = new CharArrayWriter(result[i].length() + 2); + try { + if (mstring != null) { + String rstring = (String) mstring.invoke(out, content); + assertEquals("outputString Format " + descn[i], result[i], rstring); + } + if (mstream != null) { + mstream.invoke(out, content, baos); + String rstream = new String(baos.toByteArray()); + assertEquals("output OutputStream Format " + descn[i], result[i], rstream); + } + if (mwriter != null) { + mwriter.invoke(out, content, caw); + String rwriter = String.valueOf(caw.toCharArray()); + assertEquals("output Writer Format " + descn[i], result[i], rwriter); + } + + } catch (Exception e) { + e.printStackTrace(); + fail("Failed to process " + descn[i] + " on content " + clazz + ": " + e.getMessage()); + } + } + } + + private Method getMethod(String name, Class...classes) { + try { + return XMLOutputter2.class.getMethod(name, classes); + } catch (Exception e) { + // ignore. + System.out.println("Can't find " + name + " on " + this.getClass().getName() + ": " + e.getMessage()); + } + return null; + } + + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SAttribute.java b/test/src/java/org/jdom/test/cases/serialize/SAttribute.java new file mode 100644 index 0000000..8d184f5 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SAttribute.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.Attribute; + +@SuppressWarnings("javadoc") +public class SAttribute extends Attribute { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SCDATA.java b/test/src/java/org/jdom/test/cases/serialize/SCDATA.java new file mode 100644 index 0000000..f1d78f4 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SCDATA.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.CDATA; + +@SuppressWarnings("javadoc") +public class SCDATA extends CDATA { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SComment.java b/test/src/java/org/jdom/test/cases/serialize/SComment.java new file mode 100644 index 0000000..8ac7fbd --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SComment.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.Comment; + +@SuppressWarnings("javadoc") +public class SComment extends Comment { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SDocType.java b/test/src/java/org/jdom/test/cases/serialize/SDocType.java new file mode 100644 index 0000000..2c963f6 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SDocType.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.DocType; + +@SuppressWarnings("javadoc") +public class SDocType extends DocType { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SElement.java b/test/src/java/org/jdom/test/cases/serialize/SElement.java new file mode 100644 index 0000000..c0680f2 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SElement.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.Element; + +@SuppressWarnings("javadoc") +public class SElement extends Element { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SEntityRef.java b/test/src/java/org/jdom/test/cases/serialize/SEntityRef.java new file mode 100644 index 0000000..f2f4fe4 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SEntityRef.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.EntityRef; + +@SuppressWarnings("javadoc") +public class SEntityRef extends EntityRef { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SFilter.java b/test/src/java/org/jdom/test/cases/serialize/SFilter.java new file mode 100644 index 0000000..7d815d8 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SFilter.java @@ -0,0 +1,18 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.filter2.AbstractFilter; + +@SuppressWarnings("javadoc") +public class SFilter extends AbstractFilter { + + private static final long serialVersionUID = 1L; + + @Override + public Integer filter(Object content) { + if (content instanceof Integer) { + return (Integer)content; + } + return null; + } + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SProcessingInstruction.java b/test/src/java/org/jdom/test/cases/serialize/SProcessingInstruction.java new file mode 100644 index 0000000..5430b53 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SProcessingInstruction.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.ProcessingInstruction; + +@SuppressWarnings("javadoc") +public class SProcessingInstruction extends ProcessingInstruction { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/SText.java b/test/src/java/org/jdom/test/cases/serialize/SText.java new file mode 100644 index 0000000..d614449 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/SText.java @@ -0,0 +1,10 @@ +package org.jdom.test.cases.serialize; + +import org.jdom.Text; + +@SuppressWarnings("javadoc") +public class SText extends Text { + + private static final long serialVersionUID = 1L; + +} diff --git a/test/src/java/org/jdom/test/cases/serialize/TestSubclassSerializables.java b/test/src/java/org/jdom/test/cases/serialize/TestSubclassSerializables.java new file mode 100644 index 0000000..6f4b884 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/serialize/TestSubclassSerializables.java @@ -0,0 +1,55 @@ +package org.jdom.test.cases.serialize; + +import static org.jdom.test.util.UnitTestUtil.deSerialize; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestSubclassSerializables { + + @Test + public void testFilter() { + deSerialize(new SFilter()); + } + + @Test + public void testAttribute() { + deSerialize(new SAttribute()); + } + + @Test + public void testCDATA() { + deSerialize(new SComment()); + } + + @Test + public void testComment() { + deSerialize(new SComment()); + } + + @Test + public void testDocType() { + deSerialize(new SDocType()); + } + + @Test + public void testElement() { + deSerialize(new SElement()); + } + + @Test + public void testEntityRef() { + deSerialize(new SEntityRef()); + } + + @Test + public void testProcessingInstruction() { + deSerialize(new SProcessingInstruction()); + } + + @Test + public void testText() { + deSerialize(new SText()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/special/TestIssue008.dtd b/test/src/java/org/jdom/test/cases/special/TestIssue008.dtd new file mode 100644 index 0000000..e986c5a --- /dev/null +++ b/test/src/java/org/jdom/test/cases/special/TestIssue008.dtd @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/src/java/org/jdom/test/cases/special/TestIssue008.xml b/test/src/java/org/jdom/test/cases/special/TestIssue008.xml new file mode 100644 index 0000000..b0b36ee --- /dev/null +++ b/test/src/java/org/jdom/test/cases/special/TestIssue008.xml @@ -0,0 +1,3 @@ + + + diff --git a/test/src/java/org/jdom/test/cases/special/TestIssue008ExpandEntity.java b/test/src/java/org/jdom/test/cases/special/TestIssue008ExpandEntity.java new file mode 100644 index 0000000..2788924 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/special/TestIssue008ExpandEntity.java @@ -0,0 +1,118 @@ +package org.jdom.test.cases.special; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.net.URL; + +import org.jdom.Document; +import org.jdom.JDOMException; +import org.jdom.input.SAXBuilder; +import org.jdom.output.Format; +import org.jdom.output.XMLOutputter2; +import org.jdom.test.util.FidoFetch; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestIssue008ExpandEntity { + + private final void roundTrip(boolean expand, boolean validating, String encoding, String expect) { + String docloc = "/" + this.getClass().getPackage().getName().replaceAll("\\.", "/") + "/TestIssue008.xml"; + URL docurl = FidoFetch.getFido().getURL(docloc); + + if (docurl == null) { + throw new IllegalStateException("Unable to get resource " + docloc); + } + + @SuppressWarnings("deprecation") + SAXBuilder builder = new SAXBuilder(validating); + //builder.setValidation(validating); + builder.setExpandEntities(expand); + Document doc = null; + try { + doc = builder.build(docurl); + } catch (JDOMException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + if (doc == null) { + fail("Unable to parse document, see output."); + } + + Format fmt = Format.getCompactFormat(); + if (encoding != null) { + fmt.setEncoding(encoding); + } + XMLOutputter2 xout = new XMLOutputter2(fmt); + + String actual = xout.outputString(doc.getRootElement()); + assertEquals(expect, actual); + + } + + @Test + public void testFalse() { + roundTrip(false, false, null, ""); + } + + @Test + public void testFalseUSASCII() { + roundTrip(false, false, "US-ASCII", ""); + } + + @Test + public void testFalseUTF8() { + roundTrip(false, false, "UTF-8", ""); + } + + @Test + public void testTrueUSASCII() { + roundTrip(true, false, "US-ASCII", ""); + } + + @Test + public void testTrueUTF8() { + roundTrip(true, false, "UTF-8", "\u2212"); + } + + @Test + public void testTrue() { + roundTrip(true, false, null, "\u2212"); + } + + + + @Test + public void testValidFalse() { + roundTrip(false, true, null, ""); + } + + @Test + public void testValidFalseUSASCII() { + roundTrip(false, true, "US-ASCII", ""); + } + + @Test + public void testValidFalseUTF8() { + roundTrip(false, true, "UTF-8", ""); + } + + @Test + public void testValidTrueUSASCII() { + roundTrip(true, true, "US-ASCII", ""); + } + + @Test + public void testValidTrueUTF8() { + roundTrip(true, true, "UTF-8", "\u2212"); + } + + @Test + public void testValidTrue() { + roundTrip(true, true, null, "\u2212"); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/transform/TestJDOMResult.java b/test/src/java/org/jdom/test/cases/transform/TestJDOMResult.java new file mode 100644 index 0000000..e9a3456 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/transform/TestJDOMResult.java @@ -0,0 +1,169 @@ +package org.jdom.test.cases.transform; + +import static org.junit.Assert.*; + +import java.util.ArrayList; + +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMFactory; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.UncheckedJDOMFactory; +import org.jdom.transform.JDOMResult; +import org.junit.Test; +import org.xml.sax.ContentHandler; +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.LexicalHandler; + +@SuppressWarnings("javadoc") +public class TestJDOMResult { + + /* ************************************************* + * These tests cover only a small part of JDOMResult. + * The more complex code relates to accepting the + * events fired during transformation. Those calls + * are tested in TestJDOMTransform which puts 'real' + * transformations through JDOMResult. + * *************************************************/ + + @Test + public void testJDOMResult() { + JDOMResult result = new JDOMResult(); + assertNotNull(result.getHandler()); + assertTrue(result.getHandler() == result.getLexicalHandler()); + } + + @Test + public void testSetHandlerContentHandler() { + JDOMResult result = new JDOMResult(); + ContentHandler handler = result.getHandler(); + assertNotNull(handler); + ContentHandler toset = new DefaultHandler2(); + + // we do not allow others to change the handler on the JDOMResult + // so this should do nothing. + result.setHandler(toset); + + assertTrue(handler == result.getHandler()); + } + + @Test + public void testSetLexicalHandlerLexicalHandler() { + JDOMResult result = new JDOMResult(); + LexicalHandler handler = result.getLexicalHandler(); + assertNotNull(handler); + LexicalHandler toset = new DefaultHandler2(); + + // we do not allow others to change the handler on the JDOMResult + // so this should do nothing. + result.setLexicalHandler(toset); + + assertTrue(handler == result.getLexicalHandler()); + } + + @Test + public void testGetSetFactory() { + JDOMResult result = new JDOMResult(); + JDOMFactory faca = result.getFactory(); + assertTrue(faca == null); + JDOMFactory facb = new UncheckedJDOMFactory(); + result.setFactory(facb); + assertTrue(facb == result.getFactory()); + result.setFactory(null); + assertTrue(null == result.getFactory()); + } + + @Test + public void testDocumentResult() { + // in this context, the 'source' provides us with a document. + // the expectation is that getDocument() will work to retrieve the result + // but, getNodes will only work if called first... subsequent calls + // return an empty list. + JDOMResult result = new JDOMResult(); + assertNull(result.getDocument()); + assertTrue(result.getResult().isEmpty()); + + // OK, we expect things now. + Element root = new Element("root"); + Document doc = new Document(root); + result.setDocument(doc); + // test a few times. Should not change. + assertTrue(doc == result.getDocument()); + assertTrue(doc == result.getDocument()); + assertTrue(doc == result.getDocument()); + // after a call to getDocument, getResult should be empty. + assertTrue(result.getResult().isEmpty()); + // messing with the getDocument side of things should not mess with the + // document tree. + assertTrue(root.getParent() == doc); + + // OK, reset the result. + result.setDocument(doc); + // should be something there this time + assertTrue(result.getResult().size() == 1); + // but, now it is cached too. we can get the result... + assertTrue(root == result.getResult().get(0)); + // using getResult() returns detached content, so.... + assertFalse(doc.hasRootElement()); + assertTrue(null == root.getParent()); + // further, a successful getResult() call invalidates the getDocument() + // so that will now be null. + assertNull(result.getDocument()); + } + + @Test + public void testNodesResult() { + // In this context, the 'source' provides us with a list of Nodes. + // In theory, these nodes should all be legal Element content. + JDOMResult result = new JDOMResult(); + assertNull(result.getDocument()); + assertTrue(result.getResult().isEmpty()); + + // OK, we expect things now. + Element child = new Element("child"); + Text text = new Text("text"); + ArrayList nodes = new ArrayList(2); + nodes.add(child); + nodes.add(text); + + result.setResult(nodes); + + // test a few times. Should not change. + assertTrue(nodes == result.getResult()); + assertTrue(!nodes.isEmpty()); + assertTrue(nodes == result.getResult()); + assertTrue(nodes == result.getResult()); + + // A call to getDocument should be null after a call to getResult(). + assertTrue(null == result.getDocument()); + + // after a null call to getDocument, getResult should be unchanged. + assertTrue(nodes == result.getResult()); + + // OK, reset the result. + result.setResult(nodes); + // should still be nothing there... because Text is not valid Document content + assertNull(result.getDocument()); + // but, the results are there still. + assertTrue(result.getResult().size() == 2); + + // So, make the content valid for a Document. + nodes.remove(1); // get rid of text. + // insert a PI in front of the element. + nodes.add(0, new ProcessingInstruction("jdomtest", "")); + + assertTrue(child.getParent() == null); + + // This time we expect a document result. + result.setResult(nodes); + Document doc = result.getDocument(); + assertNotNull(doc); + assertTrue (doc == child.getParent()); + + } + + + +} diff --git a/test/src/java/org/jdom/test/cases/transform/TestJDOMSource.java b/test/src/java/org/jdom/test/cases/transform/TestJDOMSource.java new file mode 100644 index 0000000..811229d --- /dev/null +++ b/test/src/java/org/jdom/test/cases/transform/TestJDOMSource.java @@ -0,0 +1,338 @@ +package org.jdom.test.cases.transform; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringWriter; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.output.SAXOutputter; +import org.jdom.output.XMLOutputter2; +import org.jdom.transform.JDOMSource; +import org.junit.Test; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.helpers.XMLFilterImpl; + +@SuppressWarnings("javadoc") +public class TestJDOMSource { + + @Test + public void testJDOMSourceDocument() { + Document doc = new Document(new Element("root")); + doc.setBaseURI("baseURI"); + JDOMSource source = new JDOMSource(doc); + assertTrue(source.getDocument() == doc); + // TODO Should the InputSource's SystemID be set? + //assertEquals("baseURI", source.getInputSource().getSystemId()); + } + + @Test + public void testJDOMSourceList() { + List nodes = new ArrayList(); + nodes.add(new Element("nodea")); + nodes.add(new Element("nodeb")); + JDOMSource source = new JDOMSource(nodes); + assertTrue(nodes == source.getNodes()); + } + + @Test + public void testJDOMSourceElement() { + Element emt = new Element("emt"); + JDOMSource source = new JDOMSource(emt); + assertTrue(emt == source.getNodes().get(0)); + } + + @Test + public void testJDOMSourceDocumentEntityResolver() { + DefaultHandler2 handler = new DefaultHandler2(); + Document doc = new Document(new Element("root")); + JDOMSource source = new JDOMSource(doc, handler); + assertTrue(doc == source.getDocument()); + assertTrue(handler == source.getXMLReader().getEntityResolver()); + } + + @Test + public void testGetSetDocument() { + Element emt = new Element("emt"); + Document doc = new Document(new Element("root")); + JDOMSource source = new JDOMSource(emt); + assertTrue(null == source.getDocument()); + assertTrue(emt == source.getNodes().get(0)); + source.setDocument(doc); + assertTrue(doc == source.getDocument()); + assertTrue(null == source.getNodes()); + } + + @Test + public void testGetSetNodes() { + Element emt = new Element("emt"); + Document doc = new Document(new Element("root")); + JDOMSource source = new JDOMSource(doc); + assertTrue(doc == source.getDocument()); + assertTrue(null == source.getNodes()); + source.setNodes(Collections.singletonList(emt)); + assertTrue(null == source.getDocument()); + assertTrue(emt == source.getNodes().get(0)); + } + + @Test + public void testGetSetInputSourceInputSource() { + Document doc = new Document(new Element("root")); + JDOMSource source = new JDOMSource(doc); + InputSource insrc = new InputSource("url"); + try { + source.setInputSource(insrc); + fail("Should not be able to change the InputSource"); + } catch (UnsupportedOperationException uoe) { + // good + } catch (Exception e) { + fail("Expected exception UnsupportedOperationException, but got " + e.getClass()); + } + } + + @Test + public void testGetSetXMLReaderXMLReader() { + Document doc = new Document(new Element("root")); + JDOMSource source = new JDOMSource(doc); + try { + XMLReader f = (XMLReader)Proxy.newProxyInstance(null, + new Class[]{XMLReader.class}, new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if (String.class == method.getReturnType()) { + return "XMLFilter Proxy"; + } + return null; + } + }); + source.setXMLReader(f); + fail("Should not be able to change the XMLReader"); + } catch (UnsupportedOperationException uoe) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected exception UnsupportedOperationException, but got " + e.getClass()); + } + + // but, you can add a Filter. + XMLReader pnt = new XMLFilterImpl(); + XMLReader filter = new XMLFilterImpl(pnt); + source.setXMLReader(filter); + XMLReader r = source.getXMLReader(); + assertTrue(filter == r); + } + + @Test + public void testInputSourceSetters() { + JDOMSource source = new JDOMSource(new Document(new Element("root"))); + InputSource isrc = source.getInputSource(); + try { + isrc.setCharacterStream(new CharArrayReader(new char[0])); + fail("should not be able to set the CharacterStream"); + } catch (UnsupportedOperationException uoe) { + // good + } catch (Exception e) { + fail ("Expected Unsupported operation exception, not " + e.getClass()); + } + try { + isrc.setByteStream(new ByteArrayInputStream(new byte[0])); + fail("should not be able to set the ByteStream"); + } catch (UnsupportedOperationException uoe) { + // good + } catch (Exception e) { + fail ("Expected Unsupported operation exception, not " + e.getClass()); + } + } + + @Test + public void testInputSourceByteStream() { + JDOMSource source = new JDOMSource(new Document(new Element("root"))); + assertNull(source.getInputSource().getByteStream()); + } + + @Test + public void testInputSourceDocCharacterStream() throws IOException { + JDOMSource source = new JDOMSource(new Document(new Element("root"))); + Document doc = source.getDocument(); + String expect = new XMLOutputter2().outputString(doc); + Reader r = source.getInputSource().getCharacterStream(); + StringWriter sw = new StringWriter(); + char[] buffer = new char[512]; + int len = 0; + while ((len = r.read(buffer)) >= 0) { + sw.write(buffer, 0, len); + } + r.close(); + String actual = sw.toString(); + assertEquals(expect, actual); + } + + @Test + public void testInputSourceListCharacterStream() throws IOException { + JDOMSource source = new JDOMSource(new Element("root")); + Element emt = (Element)source.getNodes().get(0); + String expect = new XMLOutputter2().outputString(emt); + Reader r = source.getInputSource().getCharacterStream(); + StringWriter sw = new StringWriter(); + char[] buffer = new char[512]; + int len = 0; + while ((len = r.read(buffer)) >= 0) { + sw.write(buffer, 0, len); + } + r.close(); + String actual = sw.toString(); + assertEquals(expect, actual); + } + + @Test + public void testInputSourceNullCharacterStream() { + JDOMSource source = new JDOMSource((Document)null); + Reader r = source.getInputSource().getCharacterStream(); + assertNull(r); + } + + @Test + public void testXMLReaderParse() { + JDOMSource source = new JDOMSource(new Document(new Element("root"))); + XMLReader reader = source.getXMLReader(); + try { + reader.parse(new InputSource("someurl")); + fail ("Should not be able to read a non-JDOM input source."); + } catch (SAXNotSupportedException sne) { + // good; + } catch (Exception e) { + e.printStackTrace(); + fail ("Expecting a SAXNotSupportedExcetpion, but got " + e.getClass()); + } + + } + + @Test + public void testXMLReaderParseSystemID() { + JDOMSource source = new JDOMSource(new Document(new Element("root"))); + XMLReader reader = source.getXMLReader(); + try { + reader.parse("systemID"); + fail ("Should not be able to read a SystemID."); + } catch (SAXNotSupportedException sne) { + // good; + } catch (Exception e) { + e.printStackTrace(); + fail ("Expecting a SAXNotSupportedExcetpion, but got " + e.getClass()); + } + } + + @Test + public void testXMLReaderParseOtherInputSource() { + JDOMSource source = new JDOMSource(new Document(new Element("root"))); + XMLReader reader = source.getXMLReader(); + try { + reader.parse(new InputSource("someurl")); + fail ("Should not be able to read a non-JDOM input source."); + } catch (SAXNotSupportedException sne) { + // good; + } catch (Exception e) { + e.printStackTrace(); + fail ("Expecting a SAXNotSupportedExcetpion, but got " + e.getClass()); + } + } + + @Test + public void testXMLReaderParseJDOMDocumentInputSource() throws SAXException, IOException { + JDOMSource sourcea = new JDOMSource(new Document(new Element("root"))); + JDOMSource sourceb = new JDOMSource(new Document(new Element("fubar"))); + XMLReader reader = sourcea.getXMLReader(); + assertTrue(reader instanceof SAXOutputter); + SAXOutputter readeroutputter = (SAXOutputter)reader; + final AtomicReference emtname = new AtomicReference(null); + + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void startElement(String arg0, String arg1, String arg2, Attributes arg3) throws SAXException { + emtname.set(arg1); + } + }; + + readeroutputter.setContentHandler(handler); + + reader.parse(sourceb.getInputSource()); + + assertEquals("fubar", emtname.get()); + + } + + @Test + public void testXMLReaderParseJDOMElementInputSource() throws SAXException, IOException { + JDOMSource sourcea = new JDOMSource(new Element("root")); + JDOMSource sourceb = new JDOMSource(new Element("fubar")); + XMLReader reader = sourcea.getXMLReader(); + assertTrue(reader instanceof SAXOutputter); + SAXOutputter readeroutputter = (SAXOutputter)reader; + final AtomicReference emtname = new AtomicReference(null); + + DefaultHandler2 handler = new DefaultHandler2() { + @Override + public void startElement(String arg0, String arg1, String arg2, Attributes arg3) throws SAXException { + emtname.set(arg1); + } + }; + + readeroutputter.setContentHandler(handler); + + reader.parse(sourceb.getInputSource()); + + assertEquals("fubar", emtname.get()); + + } + + @Test + public void testXMLReaderParseGarbageInputSource() { + + JDOMSource sourcea = new JDOMSource(new Element("root")); + List junk = Collections.singletonList(Double.valueOf(123.4)); + // Jump through hoops to defeat Generics. + @SuppressWarnings("cast") + List tmpa = (List)junk; + @SuppressWarnings("unchecked") + List tmpb = (List)tmpa; + JDOMSource sourceb = new JDOMSource(tmpb); + XMLReader reader = sourcea.getXMLReader(); + assertTrue(reader instanceof SAXOutputter); + SAXOutputter readeroutputter = (SAXOutputter)reader; + + DefaultHandler2 handler = new DefaultHandler2(); + + readeroutputter.setContentHandler(handler); + + try { + reader.parse(sourceb.getInputSource()); + fail ("Should not be able to parse garbage data 123.4"); + } catch (ClassCastException se) { + // good! + } catch (Exception e) { + e.printStackTrace(); + fail("Expecting SAXException, but got " + e.getClass()); + } + + } +} diff --git a/test/src/java/org/jdom/test/cases/transform/TestJDOMTransform.java b/test/src/java/org/jdom/test/cases/transform/TestJDOMTransform.java new file mode 100644 index 0000000..f33e352 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/transform/TestJDOMTransform.java @@ -0,0 +1,101 @@ +package org.jdom.test.cases.transform; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.StringReader; +import java.io.StringWriter; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.output.XMLOutputter2; +import org.jdom.transform.JDOMResult; +import org.jdom.transform.JDOMSource; +import org.junit.Ignore; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestJDOMTransform { + + private static final void checkTransform(Document doc) { + XMLOutputter2 out = new XMLOutputter2(); + String expect = out.outputString(doc); + + try { + + Transformer t = TransformerFactory.newInstance().newTransformer(); + StringWriter sw = new StringWriter(); + t.transform(new JDOMSource(doc), new StreamResult(sw)); + JDOMResult res = new JDOMResult(); + String intermediate = sw.toString() + "\n\n\n\n "; + t.transform(new StreamSource(new StringReader(intermediate)), res); + Document redone = res.getDocument(); + String actual = out.outputString(redone); + assertEquals(expect, actual); + } catch (TransformerException te) { + te.printStackTrace(); + fail (te.getMessage()); + } + } + + @Test + public void testRootAttNS() { + Document doc = new Document(); + Element e = new Element("root"); + e.setAttribute(new Attribute("att", "val", Namespace.getNamespace("ans", "mynamespace"))); + doc.addContent(e); + checkTransform(doc); + } + + + @Test + public void testSimpleDocument() { + checkTransform(new Document(new Element("root"))); + } + + @Test + @Ignore //TODO DocType does not survive transform. Can it be tested? + public void testDocumentDocType() { + DocType dt = new DocType("root"); + checkTransform(new Document(new Element("root"), dt)); + } + + @Test + public void testComplexDocument() { + // throw a lot of junk at the process... testing it all. + Document doc = new Document(); + doc.addContent(new Comment("This is a document")); + doc.addContent(new ProcessingInstruction("jdomtest", "")); + Element root = new Element("root"); + // messes up the ordering of things ... root.setAttribute(new Attribute("att", "val")); + root.setAttribute(new Attribute("att", "val", Namespace.getNamespace("ans", "attns"))); + root.addContent(new Text(" ")); + root.addContent(new Element("child", Namespace.getNamespace("nopfx"))); + root.addContent(new CDATA(" cdata ")); + root.addContent(new Comment("comment")); + Element otherchild = new Element("child", Namespace.getNamespace("cns", "childns")); + otherchild.addNamespaceDeclaration(Namespace.getNamespace("abc","oddns")); + Element grandchild = new Element("leaf", Namespace.getNamespace("abc", "oddns")); + // EntityRef will not survive transform + // grandchild.addContent(new EntityRef("myref", "systemID")); + otherchild.addContent(grandchild); + root.addContent(otherchild); + doc.addContent(root); + checkTransform(doc); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/transform/TestXSLTransformExceptn.java b/test/src/java/org/jdom/test/cases/transform/TestXSLTransformExceptn.java new file mode 100644 index 0000000..b72a815 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/transform/TestXSLTransformExceptn.java @@ -0,0 +1,33 @@ +package org.jdom.test.cases.transform; + +import static org.junit.Assert.*; + +import org.jdom.transform.XSLTransformException; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestXSLTransformExceptn { + + @Test + public void testXSLTransformException() { + XSLTransformException e = new XSLTransformException(); + assertNull(e.getCause()); + assertEquals("Error occurred in JDOM application.", e.getMessage()); + } + + @Test + public void testXSLTransformExceptionString() { + XSLTransformException e = new XSLTransformException("msg"); + assertNull(e.getCause()); + assertEquals("msg", e.getMessage()); + } + + @Test + public void testXSLTransformExceptionStringException() { + RuntimeException re = new RuntimeException("abc"); + XSLTransformException e = new XSLTransformException("msg", re); + assertTrue(re == e.getCause()); + assertEquals("msg", e.getMessage()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/transform/TestXSLTransformer.java b/test/src/java/org/jdom/test/cases/transform/TestXSLTransformer.java new file mode 100644 index 0000000..401dcad --- /dev/null +++ b/test/src/java/org/jdom/test/cases/transform/TestXSLTransformer.java @@ -0,0 +1,230 @@ +package org.jdom.test.cases.transform; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jdom.Content; +import org.jdom.DefaultJDOMFactory; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.JDOMFactory; +import org.jdom.Namespace; +import org.jdom.input.SAXBuilder; +import org.jdom.output.XMLOutputter2; +import org.jdom.transform.XSLTransformException; +import org.jdom.transform.XSLTransformer; +import org.junit.Ignore; +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestXSLTransformer { + + private static final String xslpassthrough = + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + private interface SetupTransform { + XSLTransformer buildTransformer() throws XSLTransformException; + } + + private void checkDocs(Document docexpect, Document docactual) { + XMLOutputter2 out = new XMLOutputter2(); + String expect = out.outputString(docexpect); + String actual = out.outputString(docactual); + assertEquals(expect, actual); + } + + private void checkDocs(Document docexpect, boolean addroot, List content) { + List toadd = content; + if (addroot) { + Element root = new Element(docexpect.getRootElement().getName()); + root.addContent(content); + toadd = Collections.singletonList((Content)root); + } + Document actdoc = new Document(toadd); + checkDocs(docexpect, actdoc); + } + + private void checkTransform(Document doc, Document content, SetupTransform setup) { + try { + XSLTransformer trans = setup == null + ? new XSLTransformer(new StringReader(xslpassthrough)) + : setup.buildTransformer(); + Document out = trans.transform(content); + checkDocs(doc, out); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected excepion: " + e.getMessage()); + } + } + + private void checkTransform(Document doc, List content, boolean atroot, SetupTransform setup) { + try { + XSLTransformer trans = setup == null + ? new XSLTransformer(new StringReader(xslpassthrough)) + : setup.buildTransformer(); + List out = trans.transform(content); + checkDocs(doc, atroot, out); + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected excepion: " + e.getMessage()); + } + } + + @Test + public void testXSLTransformerInputStream() { + Document doc = new Document(new Element("root")); + checkTransform(doc, doc, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return new XSLTransformer(new ByteArrayInputStream(xslpassthrough.getBytes())); + } + }); + } + + @Test + public void testXSLTransformerReader() { + Document doc = new Document(new Element("root")); + checkTransform(doc, doc, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return new XSLTransformer(new StringReader(xslpassthrough)); + } + }); + } + + @Test + public void testXSLTransformerString() throws IOException { + File tmpf = File.createTempFile("jdomxsltest", ".xml"); + try { + tmpf.deleteOnExit(); + FileWriter fw = new FileWriter(tmpf); + fw.write(xslpassthrough); + fw.flush(); + fw.close(); + final String url = tmpf.toURI().toURL().toExternalForm(); + Document doc = new Document(new Element("root")); + checkTransform(doc, doc, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return new XSLTransformer(url); + } + }); + } finally { + tmpf.delete(); + } + } + + @Test + public void testXSLTransformerFile() throws IOException { + final File tmpf = File.createTempFile("jdomxsltest", ".xml"); + try { + tmpf.deleteOnExit(); + FileWriter fw = new FileWriter(tmpf); + fw.write(xslpassthrough); + fw.flush(); + fw.close(); + Document doc = new Document(new Element("root")); + checkTransform(doc, doc, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return new XSLTransformer(tmpf); + } + }); + } finally { + tmpf.delete(); + } + } + + @Test + public void testXSLTransformerDocument() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + final Document xsldoc = builder.build(new StringReader(xslpassthrough)); + Document doc = new Document(new Element("root")); + checkTransform(doc, doc, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return new XSLTransformer(xsldoc); + } + }); + } + + @Test + public void testTransformList() throws JDOMException, IOException { + SAXBuilder builder = new SAXBuilder(); + final Document xsldoc = builder.build(new StringReader(xslpassthrough)); + Document doc = new Document(new Element("root")); + List content = new ArrayList(); + content.add(new Element("root")); + checkTransform(doc, content, false, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return new XSLTransformer(xsldoc); + } + }); + } + + @Test + @Ignore + public void testTransformDocumentEntityResolver() { + fail("Not yet implemented"); + } + + @Test + public void testGetSetFactory() throws JDOMException, IOException { + final JDOMFactory fac = new DefaultJDOMFactory() { + @Override + public Element element(final int line, final int col, String name, String prefix, String uri) { + return super.element(line, col, "xx" + name, prefix, uri); + } + @Override + public Element element(final int line, final int col, String name, String uri) { + return super.element(line, col, "xx" + name, uri); + } + @Override + public Element element(final int line, final int col, String name) { + return super.element(line, col, "xx" + name); + } + @Override + public Element element(final int line, final int col, String name, Namespace namespace) { + return super.element(line, col, "xx" + name, namespace); + } + }; + + SAXBuilder builder = new SAXBuilder(); + final Document xsldoc = builder.build(new StringReader(xslpassthrough)); + final XSLTransformer trans = new XSLTransformer(xsldoc); + assertNull(trans.getFactory()); + trans.setFactory(fac); + assertTrue(trans.getFactory() == fac); + + Document doc = new Document(new Element("root")); + Document expect = new Document(new Element("xxroot")); + checkTransform(expect, doc, new SetupTransform() { + @Override + public XSLTransformer buildTransformer() throws XSLTransformException { + return trans; + } + }); + + + } + +} diff --git a/test/src/java/org/jdom/test/cases/util/TestArrayCopy.java b/test/src/java/org/jdom/test/cases/util/TestArrayCopy.java new file mode 100644 index 0000000..d7a7bca --- /dev/null +++ b/test/src/java/org/jdom/test/cases/util/TestArrayCopy.java @@ -0,0 +1,63 @@ +package org.jdom.test.cases.util; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import org.jdom.internal.ArrayCopy; +import org.jdom.test.util.UnitTestUtil; + +@SuppressWarnings("javadoc") +public class TestArrayCopy { + + // UnitTests/compile is done from Java6 .... so + // we can compare with Arrays.copyOf() + + @Test + public void testCopyOfEArrayInt() { + final String[] val = {"a", "b", "c", "d"}; + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 3), Arrays.copyOf(val, 3))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 0), Arrays.copyOf(val, 0))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 5), Arrays.copyOf(val, 5))); + } + + @Test + public void testCopyOfRange() { + final String[] val = {"a", "b", "c", "d"}; + assertTrue(Arrays.equals(ArrayCopy.copyOfRange(val, 1, 2), Arrays.copyOfRange(val, 1, 2))); + assertTrue(Arrays.equals(ArrayCopy.copyOfRange(val, 1, 5), Arrays.copyOfRange(val, 1, 5))); + try { + ArrayCopy.copyOfRange(val, 3, 2); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testCopyOfCharArrayInt() { + final char[] val = {'a', 'b', 'c', 'd'}; + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 3), Arrays.copyOf(val, 3))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 0), Arrays.copyOf(val, 0))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 5), Arrays.copyOf(val, 5))); + } + + @Test + public void testCopyOfIntArrayInt() { + final int[] val = {1, 2, 3, 4}; + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 3), Arrays.copyOf(val, 3))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 0), Arrays.copyOf(val, 0))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 5), Arrays.copyOf(val, 5))); + } + + @Test + public void testCopyOfBooleanArrayInt() { + final boolean[] val = {true, false, true, false}; + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 3), Arrays.copyOf(val, 3))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 0), Arrays.copyOf(val, 0))); + assertTrue(Arrays.equals(ArrayCopy.copyOf(val, 5), Arrays.copyOf(val, 5))); + } + +} diff --git a/test/src/java/org/jdom/test/cases/util/TestNamespaceStack.java b/test/src/java/org/jdom/test/cases/util/TestNamespaceStack.java new file mode 100644 index 0000000..540af78 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/util/TestNamespaceStack.java @@ -0,0 +1,332 @@ +package org.jdom.test.cases.util; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.jdom.Element; +import org.jdom.Namespace; +import org.jdom.util.NamespaceStack; + +import org.junit.Test; + +@SuppressWarnings("javadoc") +public class TestNamespaceStack { + + // We do a little cheat in this test class. + // NamespaceStack is supposed to make the concept of the + // Content.getNamespacesInScope() type methods more efficient. + // so, we simply cheat by comparing the one against the other. + // We have confidence in the Content.getNamespaces* methods because they are + // tested elsewhere. + + private static final void checkIterable(Iterable abl, Namespace...values) { + int cnt = 0; + for (Namespace ns : abl) { + if (cnt >= values.length) { + fail("Unexpected extra Namespace: " + ns); + } + if (ns != values[cnt]) { + fail("We expected Namespace " + values[cnt] + " but instead we got " + ns); + } + cnt++; + } + if (cnt it = abl.iterator(); + while (--cnt >= 0) { + it.next(); + } + + assertFalse(it.hasNext()); + + try { + it.remove(); + fail("Should not be able to remove content from this iterator."); + } catch (UnsupportedOperationException uoe) { + // good. + } catch (Exception e) { + e.printStackTrace(); + fail("expected UnsupportedOperationException but got :" + e.getClass()); + } + + try { + it.next(); + fail("Should not be able to iterate beyond the iterator."); + } catch (NoSuchElementException nsee) { + // good. + } catch (Exception e) { + e.printStackTrace(); + fail("expected NoSuchElementException but got :" + e.getClass()); + } + } + + private static final void reverse(Namespace[] data) { + Namespace tmp = null; + int left = 0, right = data.length - 1; + while (left < right) { + tmp = data[left]; + data[left] = data[right]; + data[right] = tmp; + left++; + right--; + } + } + + private static final void checkIterators(NamespaceStack stack, + List lscope, List lintro) { + + Namespace[] scope = lscope.toArray(new Namespace[0]); + checkIterable(stack, scope); + Namespace[] intro = lintro.toArray(new Namespace[0]); + checkIterable(stack.addedForward(), intro); + reverse(intro); + checkIterable(stack.addedReverse(), intro); + } + + private void exercise(Element emt, NamespaceStack stack) { + List scope = emt.getNamespacesInScope(); + List intro = emt.getNamespacesIntroduced(); + + for (Namespace ns : intro) { + assertFalse(stack.isInScope(ns)); + } + + stack.push(emt); + + for (Namespace ns : intro) { + assertTrue(stack.isInScope(ns)); + } + + + checkIterators(stack, scope, intro); + + + for (Element e : emt.getChildren()) { + exercise(e, stack); + } + + checkIterators(stack, scope, intro); + stack.pop(); + } + + @Test + public void testEmptyStack() { + Namespace[] scopea = new Namespace[] {Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE}; + List scopel = Arrays.asList(scopea); + + NamespaceStack stack = new NamespaceStack(); + + checkIterators(stack, scopel, scopel); + + try { + stack.pop(); + fail("Should not be able to over-pop the stack."); + } catch (IllegalStateException ise) { + // good. + } catch (Exception e) { + e.printStackTrace(); + fail("Expected IllegalStateException but got " + e.getClass()); + } + + } + + @Test + public void testSimpleEment() { + Element root = new Element("root"); + exercise(root, new NamespaceStack()); + } + + @Test + public void testTwentyDeepEment() { + Element root = new Element("root"); + int cnt = 20; + Element e = root; + while (--cnt >= 0) { + Element c = new Element("kid", Namespace.getNamespace("Level:" + cnt)); + e.addContent(c); + e = c; + } + exercise(root, new NamespaceStack()); + } + + @Test + public void testChangeMainNoOthers() { + Element root = new Element("root", Namespace.getNamespace("rooturl")); + // child is in a different namespace but with same "" prefix. + Element child = new Element("child", Namespace.getNamespace("childurl")); + // leaf is in the NO_NAMESPACE namespace (also prefix ""); + Element leaf = new Element("leaf"); + + root.addContent(child); + child.addContent(leaf); + exercise(root, new NamespaceStack()); + } + + @Test + public void testChangeMainWithOthers() { + Namespace nsa = Namespace.getNamespace("pfxa", "nsurla"); + Namespace nsb = Namespace.getNamespace("pfxb", "nsurlb"); + Namespace nsc = Namespace.getNamespace("pfxc", "nsurlc"); + Namespace nsd = Namespace.getNamespace("pfxd", "nsurld"); + + Element root = new Element("root", Namespace.getNamespace("rooturl")); + // child is in a different namespace but with same "" prefix. + Element child = new Element("child", Namespace.getNamespace("childurl")); + // leaf is in the NO_NAMESPACE namespace (also prefix ""); + Element leaf = new Element("leaf"); + + root.addNamespaceDeclaration(nsa); + root.addNamespaceDeclaration(nsb); + root.addNamespaceDeclaration(nsc); + root.addNamespaceDeclaration(nsd); + + child.addNamespaceDeclaration(nsa); + child.addNamespaceDeclaration(nsb); + child.addNamespaceDeclaration(nsc); + child.addNamespaceDeclaration(nsd); + + // On the leaf we try the varaint of adding the namespace + // via the attributes. + leaf.setAttribute("att", "val", nsa); + leaf.setAttribute("att", "val", nsb); + leaf.setAttribute("att", "val", nsc); + leaf.setAttribute("att", "val", nsd); + + root.addContent(child); + child.addContent(leaf); + exercise(root, new NamespaceStack()); + } + + @Test + public void testChangeAltNoOthers() { + // note, they all have same prefix. + Namespace nsa = Namespace.getNamespace("pfxa", "nsurla"); + Namespace nsb = Namespace.getNamespace("pfxa", "nsurlb"); + Namespace nsc = Namespace.getNamespace("pfxa", "nsurlc"); + + Element root = new Element("root"); + Element child = new Element("child"); + Element leaf = new Element("leaf"); + + root.addNamespaceDeclaration(nsa); + + child.addNamespaceDeclaration(nsb); + + // On the leaf we try the varaint of adding the namespace + // via the attributes. + leaf.setAttribute("att", "val", nsc); + + root.addContent(child); + child.addContent(leaf); + exercise(root, new NamespaceStack()); + } + + @Test + public void testChangeAltWithOthers() { + // note, they all have same prefix. + Namespace nsa = Namespace.getNamespace("pfxa", "nsurla"); + Namespace nsb = Namespace.getNamespace("pfxa", "nsurlb"); + Namespace nsc = Namespace.getNamespace("pfxa", "nsurlc"); + Namespace alta = Namespace.getNamespace("alta", "nsalturla"); + Namespace altb = Namespace.getNamespace("altb", "nsalturlb"); + + Element root = new Element("root"); + Element child = new Element("child"); + Element leaf = new Element("leaf"); + + root.addNamespaceDeclaration(nsa); + root.addNamespaceDeclaration(alta); + root.addNamespaceDeclaration(altb); + + child.addNamespaceDeclaration(nsb); + child.addNamespaceDeclaration(alta); + child.addNamespaceDeclaration(altb); + + // On the leaf we try the varaint of adding the namespace + // via the attributes. + leaf.setAttribute("att", "val", nsc); + leaf.addNamespaceDeclaration(alta); + leaf.addNamespaceDeclaration(altb); + + root.addContent(child); + child.addContent(leaf); + exercise(root, new NamespaceStack()); + } + + + @Test + public void testSwapMainAlt() { + // note, they all have same prefix. + Namespace nsa = Namespace.getNamespace("pfxa", "nsurla"); + Namespace nsb = Namespace.getNamespace("pfxb", "nsurlb"); + + // Note how they have the same namespaces, just swap between + // Element and Attribute. + Element root = new Element("root", nsa); + root.setAttribute("att", "val", nsb); + + Element child = new Element("child", nsb); + child.setAttribute("att", "val", nsa); + + root.addContent(child); + + exercise(root, new NamespaceStack()); + } + + @Test + public void testAttributeSpecialCases() { + // note, they all have same prefix. + Namespace nsa = Namespace.getNamespace("pfxa", "nsurla"); + Namespace nsb = Namespace.getNamespace("pfxb", "nsurlb"); + + // Note how they have the same namespaces, just swap between + // Element and Attribute. + Element root = new Element("root", nsa); + // attribute has same Namespace as element + root.setAttribute("att", "val", nsa); + + Element child = new Element("child", nsb); + // Attribute has no namespace. + child.setAttribute("att", "val"); + + root.addContent(child); + + exercise(root, new NamespaceStack()); + } + + @Test + public void testAdditionalSpecialCases() { + // note, they all have same prefix. + Namespace nsa = Namespace.getNamespace("pfxa", "nsurla"); + + // Note how they have the same namespaces, just swap between + // Element and Attribute. + Element root = new Element("root", nsa); + // additional has same Namespace as element + root.addNamespaceDeclaration(nsa); + + exercise(root, new NamespaceStack()); + } + + @Test + public void testSeededConstructor() { + Namespace x = Namespace.getNamespace("X"); + Namespace y = Namespace.getNamespace("y", "Y"); + Namespace[] nsa = new Namespace[] { + x, Namespace.XML_NAMESPACE + }; + NamespaceStack nstack = new NamespaceStack(nsa); + checkIterable(nstack, nsa); + checkIterable(nstack.addedForward(), nsa); + Element emt = new Element("root", y); + emt.addNamespaceDeclaration(Namespace.NO_NAMESPACE); + nstack.push(emt); + checkIterable(nstack, y, Namespace.NO_NAMESPACE, Namespace.XML_NAMESPACE); + checkIterable(nstack.addedForward(), y, Namespace.NO_NAMESPACE); + } +} diff --git a/test/src/java/org/jdom/test/cases/util/TestReflectionConstructor.java b/test/src/java/org/jdom/test/cases/util/TestReflectionConstructor.java new file mode 100644 index 0000000..eeb613b --- /dev/null +++ b/test/src/java/org/jdom/test/cases/util/TestReflectionConstructor.java @@ -0,0 +1,62 @@ +package org.jdom.test.cases.util; + +import static org.jdom.test.util.UnitTestUtil.checkException; +import static org.jdom.test.util.UnitTestUtil.failNoException; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; + +import org.jdom.Namespace; +import org.jdom.internal.ReflectionConstructor; + +@SuppressWarnings("javadoc") +public class TestReflectionConstructor { + + @Test + public void testConstruct() { + List al = ReflectionConstructor.construct("java.util.ArrayList", List.class); + assertTrue(al.isEmpty()); + } + + @Test + public void testConstructNoSuchClass() { + try { + ReflectionConstructor.construct("java.util.Junk", List.class); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testConstructDefaultConstructor() { + try { + ReflectionConstructor.construct("java.util.Integer", Integer.class); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testConstructNoaccessConstructor() { + try { + ReflectionConstructor.construct("org.jdom.Namespace", Namespace.class); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testConstructClassCastConstructor() { + try { + ReflectionConstructor.construct("java.lang.String", Namespace.class); + failNoException(ClassCastException.class); + } catch (Exception e) { + checkException(ClassCastException.class, e); + } + } +} diff --git a/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPath.java b/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPath.java new file mode 100644 index 0000000..3de6b9c --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPath.java @@ -0,0 +1,673 @@ +package org.jdom.test.cases.xpath; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jdom.Attribute; +import org.jdom.Comment; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.test.util.UnitTestUtil; +import org.jdom.xpath.XPath; + +import org.junit.Ignore; +import org.junit.Test; + +/** + * + * @author Rolf Lear + * @deprecated replaced by XPathExpression + */ +@SuppressWarnings({"javadoc"}) +@Deprecated +public abstract class AbstractTestXPath { + + private final Document doc = new Document(); + + private final Comment doccomment = new Comment("doc comment"); + private final ProcessingInstruction docpi = new ProcessingInstruction("jdomtest", "doc"); + + private final Element main = new Element("main"); + private final Attribute mainatt = new Attribute("atta", "vala"); + private final Comment maincomment = new Comment("main comment"); + private final ProcessingInstruction mainpi = new ProcessingInstruction("jdomtest", "pi data"); + private final Text maintext1 = new Text(" space1 "); + private final Element child1emt = new Element("child"); + private final Text child1text = new Text("child1text"); + private final Text maintext2 = new Text(" space2 "); + private final Element child2emt = new Element("child"); + + private final Namespace child3nsa = Namespace.getNamespace("c3nsa", "jdom:c3nsa"); + private final Namespace child3nsb = Namespace.getNamespace("c3nsb", "jdom:c3nsb"); + private final Element child3emt = new Element("child", child3nsa); + private final Attribute child3attint = new Attribute("intatt", "-123", child3nsb); + private final Attribute child3attdoub = new Attribute("doubatt", "-123.45", child3nsb); + private final Text child3txt = new Text("c3text"); + + private final String mainvalue = " space1 child1text space2 c3text"; + + public AbstractTestXPath() { + doc.addContent(doccomment); + doc.addContent(docpi); + doc.addContent(main); + main.setAttribute(mainatt); + main.addContent(maincomment); + main.addContent(mainpi); + main.addContent(maintext1); + child1emt.addContent(child1text); + main.addContent(child1emt); + main.addContent(maintext2); + main.addContent(child2emt); + child3emt.setAttribute(child3attint); + child3emt.setAttribute(child3attdoub); + child3emt.addContent(child3txt); + main.addContent(child3emt); + } + + /** + * Create an instance of an XPath. + * Override this method to create the type of XPath instance we want to test. + * The method should add the supplied Namspace keys, and set the given variables + * on the XPath. + * @param path + * @param variables + * @param namespaces + * @return + * @throws JDOMException + */ + abstract XPath buildPath(String path) throws JDOMException; + + private XPath setupXPath(String path, Map variables, Namespace... namespaces) { + + XPath xpath = null; + + try { + xpath = buildPath(path); + + assertTrue(xpath != null); + + assertFalse(xpath.equals(null)); + assertFalse(xpath.equals(new Object())); + UnitTestUtil.checkEquals(xpath, xpath); + + assertEquals("getXPath()", path, xpath.getXPath()); + + + if (variables != null) { + for (Map.Entry me : variables.entrySet()) { + xpath.setVariable(me.getKey(), me.getValue()); + } + } + for (Namespace n : namespaces) { + xpath.addNamespace(n); + } + } catch (JDOMException jde) { + jde.printStackTrace(); + fail("Unable to create XPath " + path); + } + return xpath; + + } + + /** + * A mechanism for exercising the XPath system. + * @param xpath The xpath to run. + * @param context The context on which to run the XPath + * @param string What we expect the 'xpath' string value of the result to be (or null to skip test). + * @param number What we expect the xpath to resolve the result to be as an xpath 'Number' (or null to skip test); + * @param expect The nodes we expect from the XPath selectNodes query + */ + private static void checkXPath(XPath xpath, Object context, String value, Number number, Object...expect) { + try { + + // Check the selectNodes operation. + List result = xpath.selectNodes(context); + if (result == null) { + fail ("Got a null result from selectNodes()"); + } + String sze = result.size() == expect.length ? "" : + (" Also Different Sizes: expect=" + expect.length + " actual=" + result.size()); + int pos = 0; + for (Object o : result) { + if (pos >= expect.length) { + fail ("Results contained additional content at position " + + pos + " for xpath '" + xpath + "': " + o + sze); + } + if (o != expect[pos]) { + assertEquals("Failed result at position " + pos + + " for xpath '" + xpath + "'." + sze, expect[pos], o); + } + pos++; + } + if (pos < expect.length) { + fail ("Results are missing " + (expect.length - pos) + + " content at position " + pos + " for xpath '" + xpath + + "'. First missing content is: " + expect[pos] + sze); + } + + // Check the selectSingleNode operation. + Object o = xpath.selectSingleNode(context); + if (expect.length == 0 && o != null) { + fail("Expected XPath.selectSingleNode() to return nothing, " + + "but it returned " + o + sze); + } + if (expect.length > 0 && o == null) { + fail("XPath.selectSingleNode() returned nothing, but it should " + + "have returned " + expect[0] + sze); + } + if (expect.length > 0 && o != expect[0]) { + assertEquals("XPath.selectSingleNode() was expected to return " + + expect[0] + "' but instead it returned '" + o + "'" + sze, + expect[0], o); + } + + // Check the getValue() operation + String gotstring = xpath.valueOf(context); + if (value != null) { + assertEquals("Checking valueOf()", value, gotstring); + } + + // check numberValue() + if (number == null) { + // we do the check, ignore the result, including exceptions. + try { + xpath.numberValueOf(context); + // Great too! + } catch (JDOMException jde) { + // OK, ignore it.... + } catch (Exception e) { + e.printStackTrace(); + fail ("Expecting a value or JDOMException from numberValueOf(), but got " + e.getClass()); + } + } else { + Number gotval = xpath.numberValueOf(context); + if (!number.equals(gotval)) { + assertEquals("Numbers fail to compare!", number, gotval); + } + } + +// if (expect.length == 0 && o != null) { +// fail("Expected XPath.selectSingleNode() to return nothing, " + +// "but it returned " + o); +// } +// if (expect.length > 0 && o == null) { +// fail("XPath.selectSingleNode() returned nothing, but it should " + +// "have returned " + expect[0]); +// } +// if (expect.length > 0 && o != expect[0]) { +// fail("XPath.selectSingleNode() was expected to return " + +// expect[0] + "' but instead it returned '" + o + "'"); +// } + + } catch (JDOMException e) { + e.printStackTrace(); + fail("Could not process XPath '" + xpath + + "'. Failed with: " + e.getClass() + + ": " + e.getMessage()); + } + } + + private void checkXPath(String xpath, Object context, String value, Object...expect) { + checkXPath(setupXPath(xpath, null), context, value, null, expect); + } + + private void checkComplexXPath(String xpath, Object context, Map variables, + Collection namespaces, String value, Number number, Object...expect) { + Namespace[] nsa = namespaces == null ? new Namespace[0] : namespaces.toArray(new Namespace[0]); + checkXPath(setupXPath(xpath, variables, nsa), context, value, number, expect); + } + +// @Test +// public void testSerialization() { +// XPath xpath = setupXPath("//main", null); +// XPath xser = UnitTestUtil.deSerialize(xpath); +// assertTrue(xpath != xser); +// // TODO JaxenXPath has useless equals(). See issue #43 +// // Additionally, all XPath deserialization is done on the default +// // factory... will never be equals() if the factory used to create +// // the xpath is different. +// // UnitTestUtil.checkEquals(xpath, xser); +// assertEquals(xpath.toString(), xser.toString()); +// } + + @Test + public void testSelectDocumentDoc() { + checkXPath("/", doc, mainvalue, doc); + } + + @Test + public void testSelectDocumentMain() { + checkXPath("/", main, mainvalue, doc); + } + + @Test + public void testSelectDocumentAttr() { + checkXPath("/", child3attint, mainvalue, doc); + } + + @Test + public void testSelectDocumentPI() { + checkXPath("/", mainpi, mainvalue, doc); + } + + @Test + public void testSelectDocumentText() { + checkXPath("/", child1text, mainvalue, doc); + } + + @Test + public void testSelectMainByName() { + checkXPath("main", doc, mainvalue, main); + } + + @Test + public void testSelectMainFromDoc() { + checkXPath("//main", doc, mainvalue, main); + } + + @Test + public void testAncestorsFromRoot() { + checkXPath("ancestor::node()", doc, ""); + } + + @Test + public void testAncestorsFromMain() { + checkXPath("ancestor::node()", main, mainvalue, doc); + } + + @Test + public void testAncestorsFromChild() { + checkXPath("ancestor::node()", child1emt, mainvalue, doc, main); + } + + @Test + public void testAncestorOrSelfFromRoot() { + checkXPath("ancestor-or-self::node()", doc, mainvalue, doc); + } + + @Test + public void testAncestorOrSelfFromMain() { + checkXPath("ancestor-or-self::node()", main, mainvalue, doc, main); + } + + @Test + public void testAncestorOrSelfFromMainAttribute() { + checkXPath("ancestor-or-self::node()", mainatt, mainvalue, doc, main, mainatt); + } + + @Test + public void testAncestorOrSelfFromNamespace() { + checkXPath("ancestor-or-self::node()", child3nsa, null, child3nsa); + } + + @Test + public void testAncestorOrSelfFromChild() { + checkXPath("ancestor-or-self::node()", child1emt, mainvalue, doc, main, child1emt); + } + + + /* ************************************* + * Boolean/Double/String tests. + * ************************************* */ + + @Test + public void getXPathDouble() { + checkXPath("count( //* )", doc, null, Double.valueOf(4)); + } + + @Test + public void getXPathString() { + checkXPath("string( . )", child1emt, null, child1text.getText()); + } + + @Test + public void getXPathBoolean() { + checkXPath("count (//*) > 1", child1emt, null, Boolean.TRUE); + } + + /* ************************************* + * Element tests. + * ************************************* */ + + @Test + public void getXPathElementName() { + checkXPath("//*[name() = 'main']", doc, null, main); + } + + @Test + public void getXPathElementText() { + checkXPath("//*[string() = 'child1text']", doc, null, child1emt); + } + + + /* ************************************* + * Processing Instruction tests. + * ************************************* */ + + @Test + public void getXPathProcessingInstructionAll() { + checkXPath("//processing-instruction()", doc, null, docpi, mainpi); + } + + @Test + public void getXPathProcessingInstructionByTarget() { + checkXPath("//processing-instruction()[name() = 'jdomtest']", doc, null, docpi, mainpi); + } + + @Test + public void getXPathProcessingInstructionByData() { + checkXPath("//processing-instruction()[string() = 'doc']", doc, null, docpi); + } + + /* ************************************* + * Attribute tests. + * ************************************* */ + + @Test + @Ignore + public void getXPathAttributeAll() { + checkXPath("//@*", doc, null, mainatt, child3attint, child3attdoub); + } + + @Test + public void getXPathAttributeByName() { + checkXPath("//@*[name() = 'atta']", doc, null, mainatt); + } + + @Test + public void getXPathAttributeByValue() { + checkXPath("//@*[string() = '-123']", doc, null, child3attint); + } + + /* ************************************* + * XPath Variable tests. + * ************************************* */ + + @Test + public void testSetVariable() { + HashMap hm = new HashMap(); + String attval = mainatt.getValue(); + hm.put("valvar", attval); + checkComplexXPath("//@*[string() = $valvar]", doc, hm, null, attval, null, mainatt); + } + + /* ************************************* + * XPath namespace tests. + * ************************************* */ + @Test + public void testAttributeNamespaceAsNumberToo() { + checkComplexXPath("//@c3nsb:intatt", child3emt, null, null, + "-123", Double.valueOf(-123), child3attint); + checkComplexXPath("//@c3nsb:doubatt", child3emt, null, null, + "-123.45", Double.valueOf(-123.45), child3attdoub); + } + + @Test + public void testAddNamespaceNamespace() { + checkComplexXPath("//c3nsa:child", doc, null, Collections.singleton(child3nsa), + child3emt.getValue(), null, child3emt); + } + + @Test + @Ignore + public void testGetALLNamespaces() { + //Namespace.NO_NAMESPACE is declared earlier in documentOrder. + // so it comes first. + checkXPath("//c3nsa:child/namespace::*", child3emt, "jdom:c3nsa", + child3nsa, Namespace.NO_NAMESPACE, child3nsb, Namespace.XML_NAMESPACE); + } + + @Test + @Ignore + // This fails the Jaxen Builder because the returned attributes are not in document order. + public void testAttributesNamespace() { + checkComplexXPath("//@*[namespace-uri() = 'jdom:c3nsb']", doc, null, null, + "-123", Double.valueOf(-123), child3emt.getAttributes().toArray()); + } + + @Test + public void testXPathDefaultNamespacesFromElement() { + // the significance here is that the c3nsb namespace should already be + // available because it is in scope on the 'context' element. + // so, there should be no need to re-declare it for the xpath. + checkComplexXPath("//@c3nsb:*[string() = '-123']", child3emt, null, null, + "-123", Double.valueOf(-123), child3attint); + } + + @Test + public void testXPathDefaultNamespacesFromAttribute() { + // the significance here is that the c3nsb namespace should already be + // available because it is in scope on the 'context' element. + // so, there should be no need to re-declare it for the xpath. + checkComplexXPath("//@c3nsb:*[string() = '-123']", child3attdoub, null, null, + "-123", Double.valueOf(-123), child3attint); + } + + @Test + public void testXPathDefaultNamespacesFromText() { + // the significance here is that the c3nsb namespace should already be + // available because it is in scope on the 'context' element. + // so, there should be no need to re-declare it for the xpath. + checkComplexXPath("//@c3nsb:*[string() = '-123']", child3txt, null, null, + "-123", Double.valueOf(-123), child3attint); + } + + /* ******************************* + * Axis TestCases + * ******************************* */ + + @Test + public void testXPathAncestor() { + checkXPath("ancestor::*", child3txt, null, main, child3emt); + } + + @Test + public void testXPathAncestorOrSelf() { + checkXPath("ancestor-or-self::*", child3txt, null, main, child3emt); + } + + @Test + public void testXPathAncestorNodes() { + checkXPath("ancestor::node()", child3txt, null, doc, main, child3emt); + } + + @Test + public void testXPathAncestorOrSelfNodes() { + checkXPath("ancestor-or-self::node()", child3txt, null, doc, main, child3emt, child3txt); + } + + @Test + public void testXPathAncestorOrSelfNodesFromAtt() { + checkXPath("ancestor-or-self::node()", child3attint, null, doc, main, child3emt, child3attint); + } + + @Test + public void testXPathAttributes() { + checkXPath("attribute::*", child3emt, null, child3attint, child3attdoub); + } + + @Test + public void testXPathChild() { + checkXPath("child::*", main, null, child1emt, child2emt, child3emt); + } + + @Test + public void testXPathDescendant() { + checkXPath("descendant::*", doc, null, main, child1emt, child2emt, child3emt); + } + + @Test + public void testXPathDescendantNode() { + checkXPath("descendant::node()", doc, null, doccomment, docpi, main, + maincomment, mainpi, maintext1, child1emt, child1text, + maintext2, child2emt, child3emt, child3txt); + } + + @Test + public void testXPathDescendantOrSelf() { + checkXPath("descendant-or-self::*", doc, null, main, child1emt, child2emt, child3emt); + } + + @Test + public void testXPathFollowing() { + checkXPath("following::*", child2emt, null, child3emt); + } + + @Test + public void testXPathFollowingNode() { + checkXPath("following::node()", child2emt, null, child3emt, child3txt); + } + + @Test + public void testXPathFollowingSibling() { + checkXPath("following-sibling::*", child1emt, null, child2emt, child3emt); + } + + @Test + public void testXPathFollowingSiblingNode() { + checkXPath("following-sibling::node()", child1emt, null, maintext2, child2emt, child3emt); + } + + @Test + public void testXPathNamespaces() { + checkXPath("namespace::*", child3emt, null, child3nsa, Namespace.NO_NAMESPACE, child3nsb, Namespace.XML_NAMESPACE); + } + + @Test + public void testXPathNamespacesForText() { + checkXPath("namespace::*", maintext1, null); + } + + + @Test + public void testXPathParent() { + checkXPath("parent::*", child3emt, null, main); + } + + @Test + public void testXPathParentNode() { + checkXPath("parent::node()", child3emt, null, main); + } + + @Test + public void testXPathPreceding() { + checkXPath("preceding::*", child2emt, null, child1emt); + } + + @Test + public void testXPathPrecedingNode() { + checkXPath("preceding::node()", child2emt, null, doccomment, docpi, + maincomment, mainpi, maintext1, child1emt, child1text, maintext2); + } + + @Test + public void testXPathPrecedingSibling() { + checkXPath("preceding-sibling::*", child3emt, null, child1emt, child2emt); + } + + @Test + public void testXPathPrecedingSiblingNode() { + checkXPath("preceding-sibling::node()", child3emt, null, maincomment, + mainpi, maintext1, child1emt, maintext2, child2emt); + } + + @Test + public void testXPathSelf() { + checkXPath("self::*", child3emt, null, child3emt); + } + + + + + /* ******************************* + * Negative TestCases + * ******************************* */ + + @Test + public void testNegativeBrokenPath() { + try { + XPath.newInstance("//badaxis::dummy"); + fail("Expected a JDOMException"); + } catch (JDOMException jde) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + } + + @Test + public void testNegativeBrokenExpression() { + final String path = "//node()[string() = $novar]"; + XPath xp = null; + try { + xp = XPath.newInstance(path); + } catch (Exception e) { + e.printStackTrace(); + fail("Not expecting an exception!"); + } + assertEquals(xp.getXPath(), path); + try { + // we have not declared a value for $novar, so, expect a failure. + xp.selectSingleNode(doc); + fail("Expected a JDOMException"); + } catch (JDOMException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + try { + // we have not declared a value for $novar, so, expect a failure. + xp.selectNodes(doc); + fail("Expected a JDOMException"); + } catch (JDOMException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + try { + // we have not declared a value for $novar, so, expect a failure. + xp.valueOf(doc); + fail("Expected a JDOMException"); + } catch (JDOMException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + try { + // we have not declared a value for $novar, so, expect a failure. + xp.numberValueOf(doc); + fail("Expected a JDOMException"); + } catch (JDOMException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPathCompiled.java b/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPathCompiled.java new file mode 100644 index 0000000..5822bf5 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPathCompiled.java @@ -0,0 +1,1333 @@ +/*-- + + Copyright (C) 2012 Jason Hunter & Brett McLaughlin. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the disclaimer that follows + these conditions in the documentation and/or other materials + provided with the distribution. + + 3. The name "JDOM" must not be used to endorse or promote products + derived from this software without prior written permission. For + written permission, please contact . + + 4. Products derived from this software may not be called "JDOM", nor + may "JDOM" appear in their name, without prior written permission + from the JDOM Project Management . + + In addition, we request (but do not require) that you include in the + end-user documentation provided with the redistribution and/or in the + software itself an acknowledgement equivalent to the following: + "This product includes software developed by the + JDOM Project (http://www.jdom.org/)." + Alternatively, the acknowledgment may be graphical using the logos + available at http://www.jdom.org/images/logos. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE JDOM AUTHORS OR THE PROJECT + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + This software consists of voluntary contributions made by many + individuals on behalf of the JDOM Project and was originally + created by Jason Hunter and + Brett McLaughlin . For more information + on the JDOM Project, please see . + + */ + +package org.jdom.test.cases.xpath; + +import static org.jdom.test.util.UnitTestUtil.checkException; +import static org.jdom.test.util.UnitTestUtil.failNoException; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.Namespace; +import org.jdom.NamespaceAware; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.filter2.Filter; +import org.jdom.filter2.Filters; +import org.jdom.test.util.UnitTestUtil; +import org.jdom.xpath.XPathBuilder; +import org.jdom.xpath.XPathDiagnostic; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings({"javadoc"}) +public abstract class AbstractTestXPathCompiled { + + protected final Document doc = new Document(); + + protected final Comment doccomment = new Comment("doc comment"); + protected final ProcessingInstruction docpi = new ProcessingInstruction("jdomtest", "doc"); + + protected final Element main = new Element("main"); + protected final Attribute mainatt = new Attribute("atta", "vala"); + protected final Comment maincomment = new Comment("main comment"); + protected final ProcessingInstruction mainpi = new ProcessingInstruction("jdomtest", "pi data"); + protected final Text maintext1 = new Text(" space1 "); + protected final Element child1emt = new Element("child"); + protected final Text child1text = new Text("child1text"); + protected final Text maintext2 = new Text(" space2 "); + protected final Element child2emt = new Element("child"); + + protected final Namespace child3nsa = Namespace.getNamespace("c3nsa", "jdom:c3nsa"); + protected final Namespace child3nsb = Namespace.getNamespace("c3nsb", "jdom:c3nsb"); + protected final Element child3emt = new Element("child", child3nsa); + protected final Attribute child3attint = new Attribute("intatt", "-123", child3nsb); + protected final Attribute child3attdoub = new Attribute("doubatt", "-123.45", child3nsb); + protected final Text child3txt = new Text("c3text"); + + protected final String mainvalue = " space1 child1text space2 c3text"; + protected final boolean teststring; + + public AbstractTestXPathCompiled(boolean teststring) { + this.teststring = teststring; + doc.addContent(doccomment); + doc.addContent(docpi); + doc.addContent(main); + main.setAttribute(mainatt); + main.addContent(maincomment); + main.addContent(mainpi); + main.addContent(maintext1); + child1emt.addContent(child1text); + main.addContent(child1emt); + main.addContent(maintext2); + main.addContent(child2emt); + child3emt.setAttribute(child3attint); + child3emt.setAttribute(child3attdoub); + child3emt.addContent(child3txt); + main.addContent(child3emt); + } + + /** + * Create an instance of an XPath. + * Override this method to create the type of XPath instance we want to test. + * The method should add the supplied Namspace keys, and set the given variables + * on the XPath. + * @param path + * @param variables + * @param namespaces + * @return + * @throws JDOMException + */ + abstract XPathFactory getFactory(); + + protected XPathExpression setupXPath(Filter filter, String path, Map variables, Object context, Namespace... namespaces) { + + XPathBuilder xpath = new XPathBuilder(path, filter); + + assertFalse(xpath.equals(null)); + assertFalse(xpath.equals(new Object())); + UnitTestUtil.checkEquals(xpath, xpath); + + assertEquals("getXPath()", path, xpath.getExpression()); + + + if (variables != null) { + for (Map.Entry me : variables.entrySet()) { + xpath.setVariable(me.getKey(), me.getValue()); + } + } + if (context instanceof NamespaceAware) { + xpath.setNamespaces(((NamespaceAware)context).getNamespacesInScope()); + } + for (Namespace n : namespaces) { + xpath.setNamespace(n); + } + + return xpath.compileWith(getFactory()); + + } + + private static final void checkDiagnostic(XPathExpression xpc, Object context, List result, XPathDiagnostic diag) { + assertTrue(xpc == diag.getXPathExpression()); + assertTrue(context == diag.getContext()); + + assertTrue(null != diag.toString()); + + assertFalse(diag.isFirstOnly()); + + final List dresult = diag.getResult(); + final List draw = diag.getRawResults(); + final List dfilt = diag.getFilteredResults(); + + assertTrue(dresult.size() == result.size()); + + for (int i = 0; i < result.size(); i++) { + assertEquals(dresult.get(i), result.get(i)); + } + assertTrue(dresult.size() + dfilt.size() == draw.size()); + + int r = 0; + int f = 0; + for (int i = 0; i < draw.size(); i++) { + if (r < dresult.size() && dresult.get(r) == draw.get(i)) { + r++; + } else if (f < dfilt.size() && dfilt.get(f)== draw.get(i)) { + f++; + } else { + fail (draw.get(i) + " is neither a result nor a filtered (or is in the wrong place)"); + } + } + + } + + /** + * A mechanism for exercising the XPath system. + * @param xpath The xpath to run. + * @param context The context on which to run the XPath + * @param string What we expect the 'xpath' string value of the result to be (or null to skip test). + * @param number What we expect the xpath to resolve the result to be as an xpath 'Number' (or null to skip test); + * @param expect The nodes we expect from the XPath selectNodes query + */ + protected static void checkXPath(XPathExpression xpath, Object context, Object...expect) { + + // Check the selectNodes operation. + List result = xpath.evaluate(context); + if (result == null) { + fail ("Got a null result from selectNodes()"); + } + checkDiagnostic(xpath, context, result, xpath.diagnose(context, false)); + + boolean allns = true; + boolean allatts = true; + for (Object o : expect) { + if (!(o instanceof Namespace)) { + allns = false; + if (!allatts) { + break; + } + } + if (!(o instanceof Attribute)) { + allatts = false; + if (!allns) { + break; + } + } + } + + if (allns && expect.length > 0) { + // we expect only Namespace results. + // we use different rules.... + // for a start, we don't check on order. + // also, if we expect the NO_NAMESPACE, we don't complain if it is + // not returned. + int expectsize = expect.length; + for (Object nso : expect) { + Namespace ns = (Namespace)nso; + if (ns == Namespace.NO_NAMESPACE) { + boolean gotit = false; + for (Object o : result) { + if (o == ns) { + // got it... + gotit = true; + break; + } + } + if (!gotit) { + expectsize--; + } + } else { + boolean gotit = false; + for (Object o : result) { + if (o == ns) { + gotit = true; + break; + } + } + if (!gotit) { + fail("Expected to have item " + ns + " returned, but it was not"); + } + } + } + if (expectsize != result.size()) { + fail ("We expected " + expectsize + " Namespace results. We got " + result.size()); + } + return; + } + + if (allatts && expect.length > 0) { + // we expect only Attribute results. + // we use different rules.... + // for a start, we don't check on order. + // this really suxks, because it is only to satisfy a bug in Jaxen. + int expectsize = expect.length; + for (Object atto : expect) { + Attribute att = (Attribute)atto; + boolean gotit = false; + for (Object o : result) { + if (o == att) { + gotit = true; + break; + } + } + if (!gotit) { + fail("Expected to have item " + att + " returned, but it was not. Instead we got " + result.toString()); + } + } + if (expectsize != result.size()) { + fail ("We expected " + expectsize + " Attribute results. We got " + result.size()); + } + return; + } + + String sze = result.size() == expect.length ? "" : + (" Also Different Sizes: expect=" + expect.length + " actual=" + result.size()); + int pos = 0; + for (Object o : result) { + if (pos >= expect.length) { + fail ("Results contained additional content at position " + + pos + " for xpath '" + xpath + "': " + o + sze); + } + if (o != expect[pos]) { + assertEquals("Failed result at position " + pos + + " for xpath '" + xpath + "'." + sze, expect[pos], o); + } + pos++; + } + if (pos < expect.length) { + fail ("Results are missing " + (expect.length - pos) + + " content at position " + pos + " for xpath '" + xpath + + "'. First missing content is: " + expect[pos] + sze); + } + + // Check the selectSingleNode operation. + Object o = xpath.evaluateFirst(context); + //checkDiagnostic(Collections.singletonList(o), xpath.diagnose(context, true)); + if (expect.length == 0 && o != null) { + fail("Expected XPath.selectSingleNode() to return nothing, " + + "but it returned " + o + sze); + } + if (expect.length > 0 && o == null) { + fail("XPath.selectSingleNode() returned nothing, but it should " + + "have returned " + expect[0] + sze); + } + if (expect.length > 0 && o != expect[0]) { + assertEquals("XPath.selectSingleNode() was expected to return " + + expect[0] + "' but instead it returned '" + o + "'" + sze, + expect[0], o); + } + + } + + protected void checkXPath(String xpath, Object context, String value, Object...expect) { + checkXPath(setupXPath(Filters.fpassthrough(), xpath, null, context), context, expect); + if (teststring && value != null) { + String npath = "string(" + xpath + ")"; + checkXPath(setupXPath(Filters.fstring(), npath, null, context), context, value); + } + } + + private void checkComplexXPath(String xpath, Object context, Map variables, + Collection namespaces, String value, Number number, Object...expect) { + HashSet nset = new HashSet(); + if (namespaces != null) { + nset.addAll(namespaces); + } + if (context instanceof NamespaceAware) { + nset.addAll(((NamespaceAware)context).getNamespacesInScope()); + } + + Namespace[] nsa = nset.toArray(new Namespace[0]); + checkXPath(setupXPath(Filters.fpassthrough(), xpath, variables, context, nsa), context, expect); + if (teststring && value != null) { + String npath = "string(" + xpath + ")"; + checkXPath(setupXPath(Filters.fstring(), npath, variables, context, nsa), context, value); + } + if (teststring && number != null) { + String npath = "number(" + xpath + ")"; + checkXPath(setupXPath(Filters.fdouble(), npath, variables, context, nsa), context, number); + } + } + +// @Test +// public void testSerialization() { +// XPath xpath = setupXPath("//main", null); +// XPath xser = UnitTestUtil.deSerialize(xpath); +// assertTrue(xpath != xser); +// // TODO JaxenXPath has useless equals(). See issue #43 +// // Additionally, all XPath deserialization is done on the default +// // factory... will never be equals() if the factory used to create +// // the xpath is different. +// // UnitTestUtil.checkEquals(xpath, xser); +// assertEquals(xpath.toString(), xser.toString()); +// } + + @Test + public void testNullQuery() { + try { + getFactory().compile(null, Filters.element()); + fail("excpected NPE"); + } catch (NullPointerException noe) { + // great + } + } + + @Test + public void testNullFilter() { + try { + getFactory().compile("/", null); + fail("excpected NPE"); + } catch (NullPointerException noe) { + // great + } + } + + @Test + public void testNullNamespace() { + try { + getFactory().compile("/", Filters.element(), null, Namespace.NO_NAMESPACE, null, Namespace.XML_NAMESPACE); + fail("excpected NPE"); + } catch (NullPointerException noe) { + // great + } + } + + @Test + public void testNullNamespaceArray() { + Namespace[] nsa = null; + XPathExpression xp = getFactory().compile("/", Filters.element(), null, nsa); + assertEquals("", xp.getNamespace("").getURI()); + } + + @Test + public void testNullVariableName() { + try { + Map vars = new HashMap(); + vars.put(null, ""); + vars.put("a", "b"); + getFactory().compile("/", Filters.element(), vars); + fail("excpected NPE"); + } catch (NullPointerException noe) { + // great + } + } + + @Test + public void testDuplicatePrefix() { + try { + Namespace nsa = Namespace.getNamespace("pfx", "one"); + Namespace nsb = Namespace.getNamespace("pfx", "two"); + getFactory().compile("/", Filters.element(), null, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testRedefineNO_PREFIX() { + try { + Namespace nsa = Namespace.getNamespace("pfx", "one"); + Namespace nsb = Namespace.getNamespace("", "two"); + getFactory().compile("/", Filters.element(), null, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testDoubleSupplyNS() { + // call the same thing twice.... this is not an error + Namespace nsa = Namespace.getNamespace("pfx", "one"); + getFactory().compile("/", Filters.element(), null, Namespace.NO_NAMESPACE, nsa, Namespace.NO_NAMESPACE, nsa); + } + + @Test + public void testRedeclareNoPrefixMessageDifferentToPrefix() { + // redeclare a namespace, and the defalt namespace. The Default versions should have a + // different message + Namespace nsa = Namespace.getNamespace("pfx", "one"); + Namespace nsb = Namespace.getNamespace("pfx", "two"); + Namespace nsd = Namespace.getNamespace("", "three"); + + String ma = null; + String mb = null; + + try { + // cannot redeclare "" namespace prefix. + getFactory().compile("/", Filters.element(), null, Namespace.NO_NAMESPACE, nsd); + fail("excpected IAE"); + } catch (IllegalArgumentException iae) { + ma = iae.getMessage(); + } + + try { + // cannot redeclare "pfx" namespace prefix. + getFactory().compile("/", Filters.element(), null, nsa, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException iae) { + mb = iae.getMessage(); + } + + assertFalse(ma.equals(mb)); + } + + @Test + public void testDuplicateVariable() { + try { + Map vars = new HashMap(); + vars.put("pfa:name", "dupa"); + vars.put("pfb:name", "dupb"); + Namespace nsa = Namespace.getNamespace("pfa", "ns"); + Namespace nsb = Namespace.getNamespace("pfb", "ns"); + getFactory().compile("/", Filters.element(), vars, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testBadVariablePrefix() { + try { + Map vars = new HashMap(); + vars.put("pfa : name", "dupa"); + vars.put("pfb:name", "dupb"); + Namespace nsa = Namespace.getNamespace("pfa", "ns"); + Namespace nsb = Namespace.getNamespace("pfb", "ns"); + getFactory().compile("/", Filters.element(), vars, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testBadVariableName1() { + try { + Map vars = new HashMap(); + vars.put("pfa:123", "dupa"); + vars.put("pfb:name", "dupb"); + Namespace nsa = Namespace.getNamespace("pfa", "ns"); + Namespace nsb = Namespace.getNamespace("pfb", "ns"); + getFactory().compile("/", Filters.element(), vars, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testBadVariableName2() { + try { + Map vars = new HashMap(); + vars.put("pfa: ", "dupa"); + vars.put("pfb:name", "dupb"); + Namespace nsa = Namespace.getNamespace("pfa", "ns"); + Namespace nsb = Namespace.getNamespace("pfb", "ns"); + getFactory().compile("/", Filters.element(), vars, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testBadVariableName3() { + XPathExpression xpe = getFactory().compile("/", Filters.element()); + try { + xpe.getVariable(null); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + try { + xpe.setVariable(null, "hi"); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + } + + @Test + public void testBadVariableNamespace() { + try { + Map vars = new HashMap(); + // pfd is not defined. + vars.put("pfd:name", "dupa"); + vars.put("pfb:name", "dupb"); + Namespace nsa = Namespace.getNamespace("pfa", "ns"); + Namespace nsb = Namespace.getNamespace("pfb", "ns"); + getFactory().compile("/", Filters.element(), vars, Namespace.NO_NAMESPACE, nsa, Namespace.XML_NAMESPACE, nsb); + fail("excpected IAE"); + } catch (IllegalArgumentException noe) { + // great + } + } + + @Test + public void testGetNamespace1() { + assertEquals("", getFactory().compile("/").getNamespace("").getURI()); + } + + @Test + public void testGetNamespace2() { + XPathExpression xp = getFactory().compile("/", Filters.element(), null, Namespace.getNamespace("x", "y")); + assertEquals("y", xp.getNamespace("x").getURI()); + } + + @Test + public void testGetNamespaces() { + XPathExpression xp = getFactory().compile("/", Filters.element(), null, Namespace.getNamespace("x", "y")); + Namespace[] nsa = xp.getNamespaces(); + assertEquals("", nsa[0].getURI()); + assertEquals("y", nsa[1].getURI()); + } + + @Test + public void testGetNamespace3() { + XPathExpression xp = getFactory().compile("/", Filters.element(), null, Namespace.getNamespace("x", "y")); + try { + xp.getNamespace("hello"); + fail("expected IAE"); + } catch (IllegalArgumentException ise) { + // good. + } + } + + @Test + public void testGetVariable1() { + Map vars = new HashMap(); + vars.put("one", 1); + vars.put("nsa:one", 2); + XPathExpression xp = getFactory().compile("/", Filters.element(), vars, Namespace.getNamespace("nsa", "zzz")); + + assertEquals(1, xp.getVariable("one")); + assertEquals(1, xp.getVariable("one", Namespace.NO_NAMESPACE)); + assertEquals(2, xp.getVariable("nsa:one")); + assertEquals(2, xp.getVariable("one", Namespace.getNamespace("zzz"))); + + } + + @Test + public void testGetVariable2() { + Map vars = new HashMap(); + vars.put("one", 1); + vars.put("three", 3); + vars.put("nsa:one", 2); + vars.put("nsa:two", 2); + XPathExpression xp = getFactory().compile("/", Filters.element(), vars, Namespace.getNamespace("nsa", "zzz")); + + try { + xp.getVariable("two"); + fail("expected IAE"); + } catch (IllegalArgumentException ise) { + // good. + } + + try { + xp.getVariable("two", Namespace.NO_NAMESPACE); + fail("expected IAE"); + } catch (IllegalArgumentException ise) { + // good. + } + + try { + xp.getVariable("nsa:three"); + fail("expected IAE"); + } catch (IllegalArgumentException ise) { + // good. + } + + try { + xp.getVariable("three", Namespace.getNamespace("zzz")); + fail("expected IAE"); + } catch (IllegalArgumentException ise) { + // good. + } + } + + @Test + public void testGetNullVariableValue() { + Map vars = new HashMap(); + vars.put("one", 1); + vars.put("nsa:one", null); + XPathExpression xp = getFactory().compile("/", Filters.element(), vars, Namespace.getNamespace("nsa", "zzz")); + + assertEquals(1, xp.getVariable("one")); + assertEquals(1, xp.getVariable("one", null)); + assertEquals(1, xp.getVariable("one", Namespace.NO_NAMESPACE)); + assertEquals(null, xp.getVariable("nsa:one")); + assertEquals(null, xp.getVariable("one", Namespace.getNamespace("zzz"))); + } + + @Test + public void testSetNullVariableValue() { + Map vars = new HashMap(); + vars.put("one", 1); + vars.put("nsa:one", 2); + XPathExpression xp = getFactory().compile("/", Filters.element(), vars, Namespace.getNamespace("nsa", "zzz")); + + assertEquals(1, xp.getVariable("one")); + assertEquals(1, xp.getVariable("one", Namespace.NO_NAMESPACE)); + assertEquals(2, xp.getVariable("nsa:one")); + assertEquals(2, xp.getVariable("one", Namespace.getNamespace("zzz"))); + + assertEquals(2, xp.setVariable("one", Namespace.getNamespace("zzz"), null)); + assertEquals(1, xp.getVariable("one")); + assertEquals(1, xp.getVariable("one", Namespace.NO_NAMESPACE)); + assertEquals(null, xp.getVariable("one", Namespace.getNamespace("zzz"))); + assertEquals(null, xp.getVariable("nsa:one")); + + assertEquals(null, xp.setVariable("nsa:one", 3)); + assertEquals(1, xp.getVariable("one")); + assertEquals(1, xp.getVariable("one", Namespace.NO_NAMESPACE)); + assertEquals(3, xp.getVariable("one", Namespace.getNamespace("zzz"))); + assertEquals(3, xp.getVariable("nsa:one")); + } + + @Test + public void testGetFilter() { + Filter filter = Filters.element(); + XPathExpression xp = getFactory().compile("/", filter); + assertTrue(filter == xp.getFilter()); + } + + @Test + public void testToString() { + Map vars = new HashMap(); + vars.put("one", 1); + vars.put("nsa:one", 2); + XPathExpression xp = getFactory().compile("/", Filters.element(), vars, Namespace.getNamespace("nsa", "zzz")); + assertTrue(null != xp.toString()); + + } + + @Test + public void testClone() { + Map vars = new HashMap(); + vars.put("one", 1); + vars.put("nsa:one", 2); + XPathExpression xp = getFactory().compile("/", Filters.element(), vars, Namespace.getNamespace("nsa", "zzz")); + XPathExpression xq = xp.clone(); + assertTrue(xp != xq); + assertTrue(xp.getExpression() == xq.getExpression()); + assertTrue(xp.getVariable("one") == xq.getVariable("one")); + assertTrue(xp.getVariable("nsa:one") == xq.getVariable("one", Namespace.getNamespace("zzz"))); + assertTrue(xq.getVariable("one", Namespace.NO_NAMESPACE) == + xp.setVariable("one", "newval")); + assertEquals("newval", xp.getVariable("one")); + assertEquals(1, xq.getVariable("one")); + + } + + @Test + public void testCloneVariables() { + List lst = null; + HashMap vars = new HashMap(); + vars.put("vns:vname", "1"); + Namespace vns = Namespace.getNamespace("vns", "http://jdom.org/xpath_variable_namespace"); + XPathExpression xpathhc = XPathFactory.instance().compile( + "/main/child[1]", Filters.element()); + lst = xpathhc.evaluate(doc); + assertTrue(1 == lst.size()); + assertTrue(child1emt == lst.get(0)); + + XPathExpression xpath = XPathFactory.instance().compile( + "/main/child[position() = $vns:vname]", Filters.element(), vars, vns); + lst = xpath.evaluate(doc); + assertTrue(1 == lst.size()); + assertTrue(child1emt == lst.get(0)); + xpath.setVariable("vns:vname", "2"); + assertTrue("2" == xpath.getVariable("vname", vns)); + lst = xpath.evaluate(doc); + assertTrue(1 == lst.size()); + assertTrue(child2emt == lst.get(0)); + + XPathExpression cloned = xpath.clone(); + lst = cloned.evaluate(doc); + assertTrue(1 == lst.size()); + assertTrue(child2emt == lst.get(0)); + + cloned.setVariable("vns:vname", "1"); + assertTrue("2" == xpath.getVariable("vname", vns)); + assertTrue("1" == cloned.getVariable("vname", vns)); + lst = cloned.evaluate(doc); + assertTrue(1 == lst.size()); + assertTrue(child1emt == lst.get(0)); + } + + @Test + public void testSelectDocumentDoc() { + checkXPath("/", doc, mainvalue, doc); + } + + @Test + public void testSelectDocumentMain() { + checkXPath("/", main, mainvalue, doc); + } + + @Test + public void testSelectDocumentAttr() { + checkXPath("/", child3attint, mainvalue, doc); + } + + @Test + public void testSelectDocumentPI() { + checkXPath("/", mainpi, mainvalue, doc); + } + + @Test + public void testSelectDocumentText() { + checkXPath("/", child1text, mainvalue, doc); + } + + @Test + public void testSelectMainByName() { + checkXPath("main", doc, mainvalue, main); + } + + @Test + public void testSelectMainFromDoc() { + checkXPath("//main", doc, mainvalue, main); + } + + @Test + public void testAncestorsFromRoot() { + checkXPath("ancestor::node()", doc, ""); + } + + @Test + public void testAncestorsFromMain() { + checkXPath("ancestor::node()", main, mainvalue, doc); + } + + @Test + public void testAncestorsFromChild() { + checkXPath("ancestor::node()", child1emt, mainvalue, doc, main); + } + + @Test + public void testAncestorOrSelfFromRoot() { + checkXPath("ancestor-or-self::node()", doc, mainvalue, doc); + } + + @Test + public void testAncestorOrSelfFromMain() { + checkXPath("ancestor-or-self::node()", main, mainvalue, doc, main); + } + + @Test + public void testAncestorOrSelfFromMainAttribute() { + checkXPath("ancestor-or-self::node()", mainatt, mainvalue, doc, main, mainatt); + } + + @Test + public void testAncestorOrSelfFromNamespace() { + checkXPath("ancestor-or-self::node()", child3nsa, null, child3nsa); + } + + @Test + public void testAncestorOrSelfFromChild() { + checkXPath("ancestor-or-self::node()", child1emt, mainvalue, doc, main, child1emt); + } + + + /* ************************************* + * Boolean/Double/String tests. + * ************************************* */ + + @Test + public void getXPathDouble() { + checkXPath("count( //* )", doc, null, Double.valueOf(4)); + } + + @Test + public void getXPathString() { + checkXPath("string( . )", child1emt, null, child1text.getText()); + } + + @Test + public void getXPathBoolean() { + checkXPath("count (//*) > 1", child1emt, null, Boolean.TRUE); + } + + /* ************************************* + * Element tests. + * ************************************* */ + + @Test + public void getXPathElementName() { + checkXPath("//*[name() = 'main']", doc, null, main); + } + + @Test + public void getXPathElementText() { + checkXPath("//*[string() = 'child1text']", doc, null, child1emt); + } + + + /* ************************************* + * Processing Instruction tests. + * ************************************* */ + + @Test + public void getXPathProcessingInstructionAll() { + checkXPath("//processing-instruction()", doc, null, docpi, mainpi); + } + + @Test + public void getXPathProcessingInstructionByTarget() { + checkXPath("//processing-instruction()[name() = 'jdomtest']", doc, null, docpi, mainpi); + } + + @Test + public void getXPathProcessingInstructionByData() { + checkXPath("//processing-instruction()[string() = 'doc']", doc, null, docpi); + } + + /* ************************************* + * Attribute tests. + * ************************************* */ + + @Test + public void getXPathAttributeAll() { + checkXPath("//@*", doc, null, mainatt, child3attint, child3attdoub); + } + + @Test + public void getXPathAttributeByName() { + checkXPath("//@*[name() = 'atta']", doc, null, mainatt); + } + + @Test + public void getXPathAttributeByValue() { + checkXPath("//@*[string() = '-123']", doc, null, child3attint); + } + + /* ************************************* + * XPath Variable tests. + * ************************************* */ + + @Test + public void testSetVariable() { + HashMap hm = new HashMap(); + String attval = mainatt.getValue(); + hm.put("valvar", attval); + checkComplexXPath("//@*[string() = $valvar]", doc, hm, null, attval, null, mainatt); + } + + /* ************************************* + * XPath namespace tests. + * ************************************* */ + @Test + public void testAttributeNamespaceAsNumberToo() { + checkComplexXPath("//@c3nsb:intatt", child3emt, null, null, + "-123", Double.valueOf(-123), child3attint); + checkComplexXPath("//@c3nsb:doubatt", child3emt, null, null, + "-123.45", Double.valueOf(-123.45), child3attdoub); + } + + @Test + public void testAddNamespaceNamespace() { + checkComplexXPath("//c3nsa:child", doc, null, Collections.singleton(child3nsa), + child3emt.getValue(), null, child3emt); + } + + @Test + public void testGetALLNamespaces() { + //Namespace.NO_NAMESPACE is declared earlier in documentOrder. + // so it comes first. + // we do not specify which Namespace should be first.... + checkXPath("//c3nsa:child/namespace::*", child3emt, null, + child3nsa, Namespace.NO_NAMESPACE, child3nsb, Namespace.XML_NAMESPACE); + } + + @Test + // This fails the Jaxen Builder because the returned attributes are not in document order. + public void testAttributesNamespace() { + checkComplexXPath("//@*[namespace-uri() = 'jdom:c3nsb']", doc, null, null, + null, null /*"-123", Double.valueOf(-123)*/, child3emt.getAttributes().toArray()); + } + + @Test + public void testAttributeParent() { + checkXPath("..", mainatt, null, main); + } + + @Test + public void testXPathDefaultNamespacesFromElement() { + // the significance here is that the c3nsb namespace should already be + // available because it is in scope on the 'context' element. + // so, there should be no need to re-declare it for the xpath. + checkComplexXPath("//@c3nsb:*[string() = '-123']", child3emt, null, null, + "-123", Double.valueOf(-123), child3attint); + } + + @Test + public void testXPathDefaultNamespacesFromAttribute() { + // the significance here is that the c3nsb namespace should already be + // available because it is in scope on the 'context' element. + // so, there should be no need to re-declare it for the xpath. + checkComplexXPath("//@c3nsb:*[string() = '-123']", child3attdoub, null, null, + "-123", Double.valueOf(-123), child3attint); + } + + @Test + public void testXPathDefaultNamespacesFromText() { + // the significance here is that the c3nsb namespace should already be + // available because it is in scope on the 'context' element. + // so, there should be no need to re-declare it for the xpath. + checkComplexXPath("//@c3nsb:*[string() = '-123']", child3txt, null, null, + "-123", Double.valueOf(-123), child3attint); + } + + /* ******************************* + * Axis TestCases + * ******************************* */ + + @Test + public void testXPathAncestor() { + checkXPath("ancestor::*", child3txt, null, main, child3emt); + } + + @Test + public void testXPathAncestorOrSelf() { + checkXPath("ancestor-or-self::*", child3txt, null, main, child3emt); + } + + @Test + public void testXPathAncestorNodes() { + checkXPath("ancestor::node()", child3txt, null, doc, main, child3emt); + } + + @Test + public void testXPathAncestorOrSelfNodes() { + checkXPath("ancestor-or-self::node()", child3txt, null, doc, main, child3emt, child3txt); + } + + @Test + public void testXPathAncestorOrSelfNodesFromAtt() { + checkXPath("ancestor-or-self::node()", child3attint, null, doc, main, child3emt, child3attint); + } + + @Test + public void testXPathAttributes() { + checkXPath("attribute::*", child3emt, null, child3attint, child3attdoub); + } + + @Test + public void testXPathChild() { + checkXPath("child::*", main, null, child1emt, child2emt, child3emt); + } + + @Test + public void testXPathDescendant() { + checkXPath("descendant::*", doc, null, main, child1emt, child2emt, child3emt); + } + + @Test + public void testXPathDescendantNode() { + checkXPath("descendant::node()", doc, null, doccomment, docpi, main, + maincomment, mainpi, maintext1, child1emt, child1text, + maintext2, child2emt, child3emt, child3txt); + } + + @Test + public void testXPathDescendantOrSelf() { + checkXPath("descendant-or-self::*", doc, null, main, child1emt, child2emt, child3emt); + } + + @Test + public void testXPathFollowing() { + checkXPath("following::*", child2emt, null, child3emt); + } + + @Test + public void testXPathFollowingNode() { + checkXPath("following::node()", child2emt, null, child3emt, child3txt); + } + + @Test + public void testXPathFollowingSibling() { + checkXPath("following-sibling::*", child1emt, null, child2emt, child3emt); + } + + @Test + public void testXPathFollowingSiblingNode() { + checkXPath("following-sibling::node()", child1emt, null, maintext2, child2emt, child3emt); + } + + @Test + public void testXPathNamespaces() { + checkXPath("namespace::*", child3emt, null, child3nsa, Namespace.NO_NAMESPACE, child3nsb, Namespace.XML_NAMESPACE); + } + + @Test + public void testXPathNamespacesForText() { + checkXPath("namespace::*", maintext1, null); + } + + + @Test + public void testXPathParent() { + checkXPath("parent::*", child3emt, null, main); + } + + @Test + public void testXPathParentNode() { + checkXPath("parent::node()", child3emt, null, main); + } + + @Test + public void testXPathPreceding() { + checkXPath("preceding::*", child2emt, null, child1emt); + } + + @Test + public void testXPathPrecedingNode() { + checkXPath("preceding::node()", child2emt, null, doccomment, docpi, + maincomment, mainpi, maintext1, child1emt, child1text, maintext2); + } + + @Test + public void testXPathPrecedingSibling() { + checkXPath("preceding-sibling::*", child3emt, null, child1emt, child2emt); + } + + @Test + public void testXPathPrecedingSiblingNode() { + checkXPath("preceding-sibling::node()", child3emt, null, maincomment, + mainpi, maintext1, child1emt, maintext2, child2emt); + } + + @Test + public void testXPathSelf() { + checkXPath("self::*", child3emt, null, child3emt); + } + + @Test + public void testXPathOR() { + checkXPath("/main/node()[1] | /main/@*", child3emt, null, mainatt, maincomment); + } + + @Test + public void testXPathNoMatch() { + checkXPath("//dummy", doc, null); + } + + + /* ******************************* + * Negative TestCases + * ******************************* */ + + @Test + public void testNegativeBrokenPath() { + try { + XPathFactory.instance().compile("//badaxis::dummy"); + fail("Expected a JDOMException"); + } catch (IllegalArgumentException jde) { + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + } + + @Test + public void testNegativeBrokenExpression() { + final String path = "//node()[string() = $novar]"; + XPathExpression xp = XPathFactory.instance().compile(path); + assertEquals(xp.getExpression(), path); + try { + // we have not declared a value for $novar, so, expect a failure. + xp.evaluateFirst(doc); + fail("Expected a JDOMException"); + } catch (IllegalStateException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + try { + // we have not declared a value for $novar, so, expect a failure. + xp.evaluate(doc); + fail("Expected a JDOMException"); + } catch (IllegalStateException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + try { + // we have not declared a value for $novar, so, expect a failure. + xp.diagnose(doc, true); + fail("Expected a JDOMException"); + } catch (IllegalStateException jde) { + //System.out.println(jde.getMessage()); + // good + } catch (Exception e) { + e.printStackTrace(); + fail("Expected a JDOMException but got " + e.getClass()); + } + + } + + @Test + public void testXPathFilteredDiagnostic() { + final XPathExpression xpe = + setupXPath(Filters.element("child"), "//*", null, null); + final ArrayList res = new ArrayList(); + res.add(child1emt); + res.add(child2emt); + // child3 is not in the NO_NAMESPACE ... res.add(child3emt); + final XPathDiagnostic diag = xpe.diagnose(doc, false); + checkDiagnostic(xpe, doc, res, diag); + final XPathDiagnostic diagz = xpe.diagnose(doc, true); + // size is zero because first result ('main') does not pass the filter. + assertTrue(diagz.getResult().size() == 0); + assertTrue(diagz.toString() != null); + + final XPathExpression xpf = + setupXPath(Filters.element("main"), "//*", null, null); + final XPathDiagnostic diagf = xpf.diagnose(doc, false); + + assertTrue(main == diagf.getResult().get(0)); + assertTrue(diagf.getFilteredResults().size() == 3); + assertTrue(child1emt == diagf.getFilteredResults().get(0)); + assertTrue(child2emt == diagf.getFilteredResults().get(1)); + assertTrue(child3emt == diagf.getFilteredResults().get(2)); + + final XPathDiagnostic diagg = xpf.diagnose(doc, true); + + assertTrue(main == diagg.getResult().get(0)); + assertTrue(diagg.getFilteredResults().size() == 0); + } + + private void checkDetached(final NamespaceAware nsa) { + checkXPath(".", nsa, null, nsa); + } + + @Test + public void testDetachedAttribute() { + // non-Element content... + checkDetached(new Attribute("detached", "value")); + } + + @Test + public void testDetachedText() { + checkDetached(new Text("detached")); + } + + @Test + public void testDetachedCDATA() { + checkDetached(new CDATA("detached")); + } + + @Test + public void testDetachedProcessingInstruction() { + checkDetached(new ProcessingInstruction("detached")); + } + + @Test + public void testDetachedEntityRef() { + checkDetached(new EntityRef("detached")); + } + + @Test + public void testDetachedComment() { + checkDetached(new Comment("detached")); + + } + + @Test + public void testDetachedElement() { + checkDetached(new Element("detached")); + + } + + @Test + public void testDeepNesting() { + Element root = new Element("root"); + Document docx = new Document(root); + Element p = root; + for (int d = 15; d >= 0; d--) { + for (int i = 0; i < 64; i++) { + p.addContent(new Element("child")); + } + p = (Element)p.getContent(0); + } + XPathExpression xpe = setupXPath(Filters.element(), "//child", null, docx); + final int sz = xpe.evaluate(docx).size(); + assertEquals("Expected ", 16*64, sz); + } + + + @Test + public void testDeepBackNesting() { + // same as DeepNesting but the chold is attached to the last parent sibling instead of the first + Element root = new Element("root"); + Document docx = new Document(root); + ArrayList al = new ArrayList(); + Element p = root; + for (int d = 15; d >= 0; d--) { + for (int i = 0; i < 64; i++) { + Element k = new Element("child"); + k.addContent(new Text("")); + k.addContent(new Text("")); + al.add(k); + p.addContent(k); + } + p = (Element)p.getContent(p.getContentSize() - 1); + } + XPathExpression xpe = setupXPath(Filters.element(), "//child", null, docx); + List res = xpe.evaluate(docx); + assertEquals("Expected ", al.size(), res.size()); + Iterator ita = res.iterator(); + Iterator itb = al.iterator(); + while (ita.hasNext() && itb.hasNext()) { + final Element a = ita.next(); + final Element b = itb.next(); + assertTrue(a == b); + } + assertFalse(ita.hasNext()); + assertFalse(itb.hasNext()); + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPathHepler.java b/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPathHepler.java new file mode 100644 index 0000000..7f3dab7 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/AbstractTestXPathHepler.java @@ -0,0 +1,341 @@ +package org.jdom.test.cases.xpath; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; + +import org.junit.Test; + +import org.jdom.Attribute; +import org.jdom.CDATA; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.JDOMException; +import org.jdom.NamespaceAware; +import org.jdom.ProcessingInstruction; +import org.jdom.Text; +import org.jdom.input.SAXBuilder; +import org.jdom.test.util.FidoFetch; +import org.jdom.test.util.UnitTestUtil; +import org.jdom.xpath.XPathDiagnostic; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; +import org.jdom.xpath.XPathHelper; + +@SuppressWarnings("javadoc") +public abstract class AbstractTestXPathHepler { + + abstract XPathFactory getFactory(); + + @Test + public void testGetPathStringElement() { + Element emt = new Element("root"); + assertEquals("/root", XPathHelper.getAbsolutePath(emt)); + Element kid = new Element("kid"); + assertEquals("/kid", XPathHelper.getAbsolutePath(kid)); + emt.addContent(kid); + assertEquals("/root/kid", XPathHelper.getAbsolutePath(kid)); + } + + private static final void checkAbsolute(final XPathFactory xfac, final NamespaceAware nsa) { + String xq = null; + if (nsa instanceof Attribute) { + xq = XPathHelper.getAbsolutePath((Attribute)nsa); + } else if (nsa instanceof Content) { + xq = XPathHelper.getAbsolutePath((Content)nsa); + } else { + xq = "/"; + } + System.out.println("Running XPath for " + nsa + ": " + xq); + try { + final XPathExpression xp = xfac.compile(xq); + final XPathDiagnostic xd = xp.diagnose(nsa, false); + if (xd.getResult().size() != 1) { + fail ("expected exactly one result, not " + xd.getResult().size()); + } + if (nsa != xd.getResult().get(0)) { + fail ("Expect the only result for '" + xq + "' to be " + nsa + " but it was " + + xd.getResult().get(0)); + } + } catch (IllegalArgumentException e) { + String xxq = null; + if (nsa instanceof Attribute) { + xxq = XPathHelper.getAbsolutePath((Attribute)nsa); + } else if (nsa instanceof Content) { + xxq = XPathHelper.getAbsolutePath((Content)nsa); + } + + AssertionError ae = new AssertionError("Unable to compile expression '" + xxq + "' to node " + nsa); + ae.initCause(e); + throw ae; + } + + + } + + private static final void checkRelative(final XPathFactory xfac, final NamespaceAware nsa, final NamespaceAware nsb) { + String xq = null; + if (nsa instanceof Attribute) { + if (nsb instanceof Attribute) { + xq = XPathHelper.getRelativePath((Attribute)nsa, (Attribute)nsb); + } else { + xq = XPathHelper.getRelativePath((Attribute)nsa, (Content)nsb); + } + } else if (nsa instanceof Content) { + if (nsb instanceof Attribute) { + xq = XPathHelper.getRelativePath((Content)nsa, (Attribute)nsb); + } else { + xq = XPathHelper.getRelativePath((Content)nsa, (Content)nsb); + } + } else { + xq = "/"; + } + final XPathExpression xp = xfac.compile(xq); + final XPathDiagnostic xd = xp.diagnose(nsa, false); + if(xd.getResult().size() != 1) { + fail ("Expected single result from " + nsa + " to " + nsb + " but got " + xd); + } + if (nsb != xd.getResult().get(0)) { + fail ("Expected single result to be " + nsb + " not " + xd.getResult()); + } + + } + + /** + * This test loads up an XML document and calculates the absolute expression + * for each node, and also the expression for each node relative to *every* + * other node. It then runs every expression and ensures that the exact + * right node is selected. + * The input XML document is designed to have all sorts of tricky nodes to + * process. This ensures that all (for a limited set of 'all') combinations + * of valid input data are tested. + * + * @throws JDOMException If the document fails to parse. + * @throws IOException + */ + @Test + public void testComplex() throws JDOMException, IOException { + SAXBuilder sb = new SAXBuilder(); + sb.setExpandEntities(false); + Document doc = sb.build(FidoFetch.getFido().getURL("/complex.xml")); + final Iterator des = doc.getDescendants(); + final ArrayList allc = new ArrayList(); + final XPathFactory fac = getFactory(); + while (des.hasNext()) { + final Content c = des.next(); +// if (c.getParent() == doc && c != doc.getRootElement()) { +// // ignore document level content (except root element. +// continue; +// } + checkAbsolute(fac, c); + allc.add(c); + if (c instanceof Element) { + if (((Element) c).hasAttributes()) { + for (Attribute a : ((Element)c).getAttributes()) { + checkAbsolute(fac, a); + allc.add(a); + } + } + } + } + for (NamespaceAware nsa : allc) { + for (NamespaceAware nsb : allc) { + checkRelative(fac, nsa, nsb); + } + } + } + + @Test + public void testGetAbsolutePathAttribute() { + // testComplex() covers working case. We need to do negative testing + try { + final Attribute att = null; + XPathHelper.getAbsolutePath(att); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + final Attribute att = new Attribute("detached", "value"); + XPathHelper.getAbsolutePath(att); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testGetAbsolutePathContent() { + // testComplex() covers working case. We need to do negative testing + try { + final Text att = null; + XPathHelper.getAbsolutePath(att); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + final Text att = new Text("detached"); + XPathHelper.getAbsolutePath(att); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + } + + @Test + public void testGetRelativePathAttributeAttribute() { + // testComplex() covers working case. We need to do negative testing + try { + final Attribute atta = new Attribute("att", "value"); + final Attribute attb = null; + XPathHelper.getRelativePath(atta, attb); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + final Attribute atta = new Attribute("att", "value"); + final Attribute attb = new Attribute("detached", "value"); + XPathHelper.getRelativePath(atta, attb); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + final Attribute atta = null; + final Attribute attb = new Attribute("att", "value"); + XPathHelper.getRelativePath(atta, attb); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testGetRelativePathContentAttribute() { + // testComplex() covers working case. We need to do negative testing + try { + final Element root = new Element("root"); + final Attribute att = null; + XPathHelper.getRelativePath(root, att); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + final Element root = new Element("root"); + final Attribute att = new Attribute("detached", "value"); + XPathHelper.getRelativePath(root, att); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + // testComplex() covers working case. We need to do negative testing + try { + final Element root = null; + final Attribute att = new Attribute("att", "value"); + XPathHelper.getRelativePath(root, att); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testGetRelativePathAttributeContent() { + // testComplex() covers working case. We need to do negative testing + try { + final Attribute att = null; + final Element root = new Element("root"); + XPathHelper.getRelativePath(att, root); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + final Attribute att = new Attribute("detached", "value"); + final Element root = new Element("root"); + XPathHelper.getRelativePath(att, root); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + final Attribute att = new Attribute("detached", "value"); + final Element root = null; + XPathHelper.getRelativePath(att, root); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + @Test + public void testGetRelativePathContentContent() { + // testComplex() covers working case. We need to do negative testing + try { + final Element root = new Element("root"); + final Text att = null; + XPathHelper.getRelativePath(root, att); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + final Element root = new Element("root"); + final Text att = new Text("detached"); + XPathHelper.getRelativePath(root, att); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + // no common ancestor + final Element roota = new Element("root"); + final Element rootb = new Element("root"); + XPathHelper.getRelativePath(roota, rootb); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + final Element roota = null; + final Element rootb = new Element("root"); + XPathHelper.getRelativePath(roota, rootb); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + } + + private void checkDetached(final XPathFactory fac, final NamespaceAware nsa) { + try { + checkAbsolute(fac, nsa); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + checkRelative(fac, nsa, nsa); + } + + @Test + public void testDetached() { + // non-Element content... + final XPathFactory fac = getFactory(); + checkDetached(fac, new Text("detached")); + checkDetached(fac, new CDATA("detached")); + checkDetached(fac, new Attribute("detached", "value")); + checkDetached(fac, new ProcessingInstruction("detached")); + checkDetached(fac, new EntityRef("detached")); + checkDetached(fac, new Comment("detached")); + + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestDefaultXPathHelper.java b/test/src/java/org/jdom/test/cases/xpath/TestDefaultXPathHelper.java new file mode 100644 index 0000000..876c280 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestDefaultXPathHelper.java @@ -0,0 +1,11 @@ +package org.jdom.test.cases.xpath; + +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings("javadoc") +public class TestDefaultXPathHelper extends AbstractTestXPathHepler { + @Override + XPathFactory getFactory() { + return XPathFactory.instance(); + } +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestJavaCompiled.java b/test/src/java/org/jdom/test/cases/xpath/TestJavaCompiled.java new file mode 100644 index 0000000..1db41f6 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestJavaCompiled.java @@ -0,0 +1,86 @@ +package org.jdom.test.cases.xpath; + +import org.junit.Test; + +import org.jdom.contrib.xpath.java.JavaXPathFactory; +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings({"javadoc"}) +public class TestJavaCompiled extends AbstractTestXPathCompiled { + + public TestJavaCompiled() { + super(false); + } + + private static final XPathFactory myfac = new JavaXPathFactory(); + + @Override + XPathFactory getFactory() { + return myfac; + } + + @Override + public void testAncestorOrSelfFromNamespace() { + // nothing + } + + @Override + public void getXPathDouble() { + // nothing + } + + @Override + public void getXPathString() { + // nothing + } + + @Override + public void getXPathBoolean() { + // nothing + } + + @Override + @Test + public void testXPathPrecedingNode() { + // we do not get items outside the root node for Document stuff. + checkXPath("preceding::node()", child2emt, null, + maincomment, mainpi, maintext1, child1emt, child1text, maintext2); + } + + + @Override + public void testDetachedAttribute() { + // TODO Not Supported + } + + @Override + public void testDetachedText() { + // TODO Not Supported + } + + @Override + public void testDetachedCDATA() { + // TODO Not Supported + } + + @Override + public void testDetachedProcessingInstruction() { + // TODO Not Supported + } + + @Override + public void testDetachedEntityRef() { + // TODO Not Supported + } + + @Override + public void testDetachedComment() { + // TODO Not Supported + } + + @Override + public void testDetachedElement() { + // TODO Not Supported + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestJaxenCompiled.java b/test/src/java/org/jdom/test/cases/xpath/TestJaxenCompiled.java new file mode 100644 index 0000000..a0d7bd7 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestJaxenCompiled.java @@ -0,0 +1,49 @@ +package org.jdom.test.cases.xpath; + +import org.junit.Ignore; +import org.junit.Test; + +import org.jdom.Comment; +import org.jdom.Element; +import org.jdom.xpath.XPathFactory; +import org.jdom.xpath.jaxen.JaxenXPathFactory; + +@SuppressWarnings({"javadoc"}) +public class TestJaxenCompiled extends AbstractTestXPathCompiled { + + public TestJaxenCompiled() { + super(true); + } + + private static final XPathFactory myfac = new JaxenXPathFactory(); + + @Override + XPathFactory getFactory() { + return myfac; + } + + public static void main(String[] args) { + System.setProperty("jaxp.debug", "true"); + javax.xml.xpath.XPathFactory myfacx = javax.xml.xpath.XPathFactory.newInstance(); + System.out.println(myfacx); + } + + @Override + @Test + @Ignore + public void testXPathOR() { + // JAXEN Does not support document order for unions.... + super.testXPathOR(); + } + + @Test + @Ignore + public void testSpecialOR() { + Element m = new Element("main"); + m.setAttribute("att", "value"); + m.addContent(new Comment("comment")); + checkXPath("/main/node()[1] | /main/@*", main, null, m.getAttribute("att"), m.getContent(0)); + } + + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestJaxenXPathHelper.java b/test/src/java/org/jdom/test/cases/xpath/TestJaxenXPathHelper.java new file mode 100644 index 0000000..0945c09 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestJaxenXPathHelper.java @@ -0,0 +1,12 @@ +package org.jdom.test.cases.xpath; + +import org.jdom.xpath.XPathFactory; +import org.jdom.xpath.jaxen.JaxenXPathFactory; + +@SuppressWarnings("javadoc") +public class TestJaxenXPathHelper extends AbstractTestXPathHepler { + @Override + XPathFactory getFactory() { + return XPathFactory.newInstance(JaxenXPathFactory.class.getName()); + } +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestLocalJaxenXPath.java b/test/src/java/org/jdom/test/cases/xpath/TestLocalJaxenXPath.java new file mode 100644 index 0000000..2373623 --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestLocalJaxenXPath.java @@ -0,0 +1,22 @@ +package org.jdom.test.cases.xpath; + +import org.jdom.JDOMException; +import org.jdom.xpath.XPath; +import org.jdom.xpath.jaxen.JDOMXPath; + +/** + * + * @author Rolf Lear + * @deprecated in lieu of TestJaxenCompiled + */ +@Deprecated +public class TestLocalJaxenXPath extends AbstractTestXPath { + + @Override + @Deprecated + XPath buildPath(String path) throws JDOMException { + final XPath ret = new JDOMXPath(path); + return ret; + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestXPathBuilder.java b/test/src/java/org/jdom/test/cases/xpath/TestXPathBuilder.java new file mode 100644 index 0000000..e12069d --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestXPathBuilder.java @@ -0,0 +1,192 @@ +package org.jdom.test.cases.xpath; + +import static org.junit.Assert.*; + +import java.util.Collections; + +import org.junit.Test; + +import org.jdom.Namespace; +import org.jdom.filter2.Filters; +import org.jdom.test.util.UnitTestUtil; +import org.jdom.xpath.XPathBuilder; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings("javadoc") +public class TestXPathBuilder { + + @Test + public void testXPathBuilder() { + try { + new XPathBuilder(null, Filters.fpassthrough()).toString(); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + new XPathBuilder("/", null).toString(); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + assertNotNull(new XPathBuilder("/", Filters.fpassthrough()).toString()); + } + + @Test + public void testGetSetVariable() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + try { + xpb.getVariable(null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + xpb.setVariable(null, ""); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + xpb.setVariable(null, ""); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + assertNull(xpb.getVariable("hello")); + assertTrue(xpb.setVariable("name", "")); + assertEquals("", xpb.getVariable("name")); + assertFalse(xpb.setVariable("name", "xxx")); + assertEquals("xxx", xpb.getVariable("name")); + } + + @Test + public void testSetNamespaceStringString() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + try { + xpb.setNamespace("", "hello"); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + xpb.setNamespace("", null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + try { + xpb.setNamespace(null, ""); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + + assertFalse(xpb.setNamespace("", "")); + + assertEquals(Namespace.NO_NAMESPACE, xpb.getNamespace("")); + + assertNull(xpb.getNamespace("hello")); + Namespace hello = Namespace.getNamespace("h", "hello"); + assertTrue(xpb.setNamespace("h", "hello")); + assertTrue(hello == xpb.getNamespace("h")); + + try { + xpb.getNamespace(null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + + } + + @Test + public void testSetNamespaceNamespace() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + try { + Namespace nsx = Namespace.getNamespace("", "hello"); + xpb.setNamespace(nsx); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + xpb.setNamespace(null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + + assertFalse(xpb.setNamespace(Namespace.NO_NAMESPACE)); + + assertEquals(Namespace.NO_NAMESPACE, xpb.getNamespace("")); + + assertNull(xpb.getNamespace("hello")); + Namespace hello = Namespace.getNamespace("h", "hello"); + assertTrue(xpb.setNamespace(hello)); + assertTrue(hello == xpb.getNamespace("h")); + } + + @Test + public void testSetNamespaces() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + try { + Namespace nsx = Namespace.getNamespace("", "hello"); + xpb.setNamespaces(Collections.singleton(nsx)); + UnitTestUtil.failNoException(IllegalArgumentException.class); + } catch (Exception e) { + UnitTestUtil.checkException(IllegalArgumentException.class, e); + } + try { + xpb.setNamespaces(null); + UnitTestUtil.failNoException(NullPointerException.class); + } catch (Exception e) { + UnitTestUtil.checkException(NullPointerException.class, e); + } + + assertFalse(xpb.setNamespaces(Collections.singletonList(Namespace.NO_NAMESPACE))); + + assertEquals(Namespace.NO_NAMESPACE, xpb.getNamespace("")); + + assertNull(xpb.getNamespace("hello")); + Namespace hello = Namespace.getNamespace("h", "hello"); + assertTrue(xpb.setNamespaces(Collections.singletonList(hello))); + assertTrue(hello == xpb.getNamespace("h")); + Namespace hellp = Namespace.getNamespace("h", "hellp"); + assertFalse(xpb.setNamespaces(Collections.singletonList(hellp))); + } + + @Test + public void testGetFilter() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + assertTrue(xpb.getFilter() == Filters.fpassthrough()); + } + + @Test + public void testGetExpression() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + assertEquals("/", xpb.getExpression()); + } + + @Test + public void testCompileWith() { + XPathBuilder xpb = + new XPathBuilder("/", Filters.fpassthrough()); + xpb.setNamespace("p", "uri"); + xpb.setVariable("p:var", "value"); + XPathExpression xpe = xpb.compileWith(XPathFactory.instance()); + assertEquals("/", xpe.getExpression()); + assertEquals(Filters.fpassthrough(), xpe.getFilter()); + assertEquals("", xpe.getNamespace("").getURI()); + assertEquals("uri", xpe.getNamespace("p").getURI()); + assertEquals("value", xpe.getVariable("var", Namespace.getNamespace("uri"))); + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestXPathFactory.java b/test/src/java/org/jdom/test/cases/xpath/TestXPathFactory.java new file mode 100644 index 0000000..e6ed86c --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestXPathFactory.java @@ -0,0 +1,49 @@ +package org.jdom.test.cases.xpath; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import org.jdom.Element; +import org.jdom.filter2.Filters; +import org.jdom.xpath.XPathExpression; +import org.jdom.xpath.XPathFactory; +import org.jdom.xpath.jaxen.JaxenXPathFactory; + +@SuppressWarnings("javadoc") +public class TestXPathFactory { + + @Test + public void testNewInstance() { + XPathFactory xpf = XPathFactory.instance(); + assertNotNull(xpf); + assertTrue(xpf != XPathFactory.newInstance(xpf.getClass().getName())); + XPathExpression xp = xpf.compile("."); + Element emt = new Element("root"); + assertTrue(emt == xp.evaluateFirst(emt)); + } + + @Test + public void testNewInstanceString() { + XPathFactory xpf = XPathFactory.newInstance(JaxenXPathFactory.class.getName()); + assertNotNull(xpf); + assertTrue(xpf != XPathFactory.newInstance(JaxenXPathFactory.class.getName())); + XPathExpression xp = xpf.compile("."); + Element emt = new Element("root"); + assertTrue(emt == xp.evaluateFirst(emt)); + } + + @Test + public void testNewInstanceCompileNSList() { + XPathFactory xpf = XPathFactory.newInstance(JaxenXPathFactory.class.getName()); + assertNotNull(xpf); + assertTrue(xpf != XPathFactory.newInstance(JaxenXPathFactory.class.getName())); + XPathExpression xp = xpf.compile("."); + Element emt = new Element("root"); + assertTrue(emt == xp.evaluateFirst(emt)); + xp = xpf.compile(".", Filters.element(), null, emt.getNamespacesInScope()); + assertTrue(emt == xp.evaluateFirst(emt)); + } + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestXalanCompiled.java b/test/src/java/org/jdom/test/cases/xpath/TestXalanCompiled.java new file mode 100644 index 0000000..9b3079e --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestXalanCompiled.java @@ -0,0 +1,72 @@ +package org.jdom.test.cases.xpath; + +import org.junit.Test; + +import org.jdom.contrib.xpath.xalan.XalanXPathFactory; +import org.jdom.xpath.XPathFactory; + +@SuppressWarnings({"javadoc"}) +public class TestXalanCompiled extends AbstractTestXPathCompiled { + + public TestXalanCompiled() { + super(true); + } + + private static final XPathFactory myfac = new XalanXPathFactory(); + + @Override + XPathFactory getFactory() { + return myfac; + } + + @Override + public void testAncestorOrSelfFromNamespace() { + // nothing... can't set a Namespace as a context in Xalan; + } + + @Override + @Test + public void testXPathPrecedingNode() { + // we do not get items outside the root node for Document stuff. + checkXPath("preceding::node()", child2emt, null, + maincomment, mainpi, maintext1, child1emt, child1text, maintext2); + } + + @Override + public void testDetachedAttribute() { + // TODO Not Supported + } + + @Override + public void testDetachedText() { + // TODO Not Supported + } + + @Override + public void testDetachedCDATA() { + // TODO Not Supported + } + + @Override + public void testDetachedProcessingInstruction() { + // TODO Not Supported + } + + @Override + public void testDetachedEntityRef() { + // TODO Not Supported + } + + @Override + public void testDetachedComment() { + // TODO Not Supported + } + + @Override + public void testDetachedElement() { + // TODO Not Supported + } + + + +} diff --git a/test/src/java/org/jdom/test/cases/xpath/TestXpath.java b/test/src/java/org/jdom/test/cases/xpath/TestXpath.java new file mode 100644 index 0000000..77b56cc --- /dev/null +++ b/test/src/java/org/jdom/test/cases/xpath/TestXpath.java @@ -0,0 +1,60 @@ +package org.jdom.test.cases.xpath; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.junit.Test; + +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.JDOMException; +import org.jdom.xpath.XPath; +import org.jdom.xpath.jaxen.JDOMXPath; + +/** + * + * @author Rolf Lear + * @deprecated old XPath + */ +@SuppressWarnings({"javadoc"} ) +@Deprecated +public class TestXpath { + /* + * Test the static methods on the abstract class. + */ + + @Test + public void testNewInstance() { + try { + Document doc = new Document(new Element("main")); + XPath xp = XPath.newInstance("/"); + assertTrue(doc == xp.selectSingleNode(doc)); + } catch (JDOMException e) { + e.printStackTrace(); + fail("Could not process XPath.newInstance()"); + } + } + + @Test + public void testSetXPathClass() throws JDOMException { + XPath.setXPathClass(JDOMXPath.class); + } + + @Test + public void testSelectNodesObjectString() throws JDOMException { + Document doc = new Document(new Element("main")); + List lst = XPath.selectNodes(doc, "/"); + assertTrue(lst.size() == 1); + assertTrue(doc == lst.get(0)); + } + + @Test + public void testSelectSingleNodeObjectString() throws JDOMException { + Document doc = new Document(new Element("main")); + Object ret = XPath.selectSingleNode(doc, "/"); + assertTrue(doc == ret); + } + +} diff --git a/test/src/java/org/jdom/test/util/AbstractTestList.java b/test/src/java/org/jdom/test/util/AbstractTestList.java new file mode 100644 index 0000000..276a2e7 --- /dev/null +++ b/test/src/java/org/jdom/test/util/AbstractTestList.java @@ -0,0 +1,1282 @@ +package org.jdom.test.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.jdom.test.util.UnitTestUtil.*; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; + +import org.junit.Test; + +import org.jdom.internal.ArrayCopy; + +/** + * This base class can be used to test multiple implementations of List + * It is reusable for different list types, and does the 'normal' type testing. + * + * + * + * @author Rolf Lear + * + */ +@SuppressWarnings({ "unchecked", "javadoc" }) +public abstract class AbstractTestList { + + /** + * The following is a list of somewhat binary-progressive prime numbers. + * sequence is +2, +2, +2, +2, +4, +4, +4, +4, +8, +8, +8, +8, + * +16, +16, +16, +16, +32, ...... + * given the above sequence, get the first prime number that is larger than + * the sequence value. + */ + private static final int[] BIGPRIMES = new int[] {3, 5, 7, 11, 13, 17, 23, + 29, 37, 41, 53, 59, 73, 89, 107, 127, 157, 191, 223, 251, 313, 379, 443, + 509, 641, 761, 907, 1019, 1277, 1531, 1787, 2053, 2557, 3067, 3581, + 4091, 5113, 6143, 7177, 8191, 10243, 12281, 14341, 16381, 20477, 24571, + 28669, 32771, 40961, 49157, 57347, 65537, 81919, 98297, 114689, 131071, + 163841, 196613, 229373, 262139, 327673, 393209, 458747, 524287, 655357, + 786431, 917503, 1048571}; + + private static final int[][] SMALLSHUFFLE = new int[][] { + new int[]{}, + new int[]{0}, + new int[]{1,0}, + new int[]{2, 0, 1}, + new int[]{2, 0, 3, 1}, + new int[]{4, 1, 2, 0, 3}, + new int[]{3, 0, 2, 4, 5, 1} + }; + + private final boolean i_nullok; + private final Class i_tclass; + + /** + * Generalised constructor for testing a list. + * @param tclass all content should be an instance (or subtype) of this class + * @param nullok are null values allowed on the list. + * @param samplecontent some sample data to load in to the list. The more, the better. + */ + public AbstractTestList(Class tclass, boolean nullok) { + super(); + i_nullok = nullok; + i_tclass = tclass; + } + + /** + * Create a list of the type to test. It should be empty. + * @return the list to test. + */ + public abstract List buildEmptyList(); + + /** + * This is sample data that should be legal in the list. + * There should be at least 10 or so elements. + * @return the sample data. + */ + public abstract T[] buildSampleContent(); + + /** + * This should contain at least one additional valid entry + * that can be legally be added to the list after it is already populated + * with the sample data. + * @return the extra data. + */ + public abstract T[] buildAdditionalContent(); + + /** + * This should contain data that is of the correct generic type of the data + * but has some property that makes it illegal. This can be empty.... + * @return the IllegalArgument test data. + */ + public abstract T[] buildIllegalArgumentContent(); + /** + * This list should be of content that is of the wrong class (a class not + * compatible with + * @return the ClassCast test data. + */ + public abstract Object[] buildIllegalClassContent(); + + + /* ********************************* + * Private support methods + * ********************************* */ + + /** + * Create an array of the required type with the required length + * @param length The length of the array to create. + * @return the created array. + */ + protected final T[] buildArray(int length) { + return (T[])Array.newInstance(i_tclass, length); + } + + /** + * Remove an element from an array. + * @param content The original content. + * @param index The element to remove + * @return a new array with the specified element removed. + */ + protected final T[] arrayRemove(T[] content, int index) { + if (index < 0 || index >= content.length) { + throw new IllegalArgumentException("Can not have index " + index + + " when there are only " + content.length + " elements."); + } + T[] ret = ArrayCopy.copyOf(content, content.length - 1); + System.arraycopy(content, index + 1, ret, index, ret.length - index); + return ret; + } + + /** + * Insert an element in to the content at the specified index. + * @param content The base content to add to. + * @param index The position to insert (items from this position will be + * moved to the right). Using content.lenght will add to the end. + * @param insert The value to insert + * @return The amended content array + */ + protected final T[] arrayInsert(T[] content, int index, T...insert) { + if (index < 0 || index > content.length) { + throw new IllegalArgumentException("Can not use index " + index + + " when there is only " + content.length + " content."); + } + if (insert == null) { + throw new NullPointerException("Can not have a null insert vararg array"); + } + T[] ret = ArrayCopy.copyOf(content, content.length + insert.length); + System.arraycopy(ret, index, ret, index+insert.length, content.length - index); + System.arraycopy(insert, 0, ret, index, insert.length); + return ret; + } + + /** + * Reverse the contents of an array. + * @param content The array to reverse. + * @return a new array with the same content as the input, but in reverse + * order + */ + protected final T[] arrayReverse(T[] content) { + T[] ret = ArrayCopy.copyOf(content, content.length); + for (int i = (content.length - 1) / 2; i >= 0; i--) { + final T tmp = ret[i]; + ret[i] = ret[ret.length - 1 - i]; + ret[ret.length - 1 - i] = tmp; + } + return ret; + } + + private final int pickPrime(final int len) { + if (len < SMALLSHUFFLE.length) { + throw new IllegalStateException("Should have a prime set already"); + } + int pindex = 0; + while (pindex < BIGPRIMES.length && BIGPRIMES[pindex] <= (len / 2)) { + pindex++; + } + if (pindex >= BIGPRIMES.length) { + throw new IllegalStateException("Unable to create a shuffled order " + + "for that many elements: " + len); + } + return BIGPRIMES[pindex]; + } + + /* + * helper method for shuffle(). + */ + private final int shuffleCompute(final int offset, final int len, final int prime) { + // Integer.MAX_INT is a prime number... but too big + // Using a Prime in this way guarantees that we loop through all the values + // in the sequence without having duplicates. + return (offset + prime) % len; + } + + /** + * Generate a (very) pseudo-random order of a given length. + * The shuffled order is always repeatable, predictable, but also + * appears 'random'. This is useful so that we can compare strange-ordered + * inserts/removes/sets. + * @param len How many elements to include. + * @return An array of int. Each value will be unique, from 0 to len-1 + */ + protected final int[] shuffle(final int len) { + if (len < SMALLSHUFFLE.length) { + return ArrayCopy.copyOf(SMALLSHUFFLE[len], len); + } + final int prime = pickPrime(len); + int[] ret = new int[len]; + int c = shuffleCompute(0, len, prime); + for (int i = len - 1; i >= 0; i--) { + c = shuffleCompute(c, len, prime); + if (ret[c] != 0) { + // this should never happen, but this is a double-check + // guarantee that we never miss values in the sequence. + throw new IllegalStateException("Oops"); + } + ret[c] = i; + } + return ret; + } + + /** + * Run a list though the basic paces of operation. + * @param list the List to exercise. + * @param content the content (in the order) that we expect in the list. + */ + protected final void exercise(List list, T...content) { + assertTrue("List is null", list != null); + assertTrue("Content is null", content != null); + assertTrue(content.length == list.size()); + assertTrue(list.toString() != null); + if (content.length == 0) { + assertTrue(list.size() == 0); + assertTrue(list.isEmpty()); + } else { + assertTrue(list.size() > 0); + assertFalse(list.isEmpty()); + } + + for (int i = 0; i < content.length; i++) { + if (list.get(i) != content[i]) { + fail(String.format("We expect element in list at position %d to be %s", + i, content[i])); + } + int pos = list.indexOf(content[i]); + assertTrue(pos >= 0); + if (pos != i) { + // may be duplicates in the list.... + assertTrue(pos < i); // but pos must be first... + assertEquals(content[pos], content[i]); + } + assertTrue(list.contains(content[i])); + } + + for (int i = content.length - 1; i >= 0; i--) { + assertTrue(list.get(i) == content[i]); + int pos = list.lastIndexOf(content[i]); + assertTrue(pos >= 0); + if (pos != i) { + // may be duplicates in the list.... + assertTrue(pos > i); // but pos must be later... + assertEquals(content[pos], content[i]); + } + } + + quickCheck(list, content); + + { + // Check the iteration code. + // the iterator implementation can be different to the ListIterator + Iterator it = list.iterator(); + try { + it.remove(); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + int i = 0; + while (i < content.length) { + assertTrue(it.hasNext()); + assertTrue(it.next() == content[i]); + it.remove(); + try { + it.remove(); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + i++; + } + assertFalse(it.hasNext()); + try { + it.next(); + failNoException(NoSuchElementException.class); + } catch (Exception e) { + checkException(NoSuchElementException.class, e); + } + + try { + it.remove(); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + + for (T d : content) { + list.add(d); + } + + } + + quickCheck(list, content); + + { + //Read-Only List iteration through list contents. + for (int origin = 0; origin <= content.length; origin++) { + // start at every place + ListIterator li = list.listIterator(origin); + + for (int i = origin; i < content.length; i++) { + assertTrue(li.hasNext()); + assertTrue(li.nextIndex() == i); + assertTrue(li.previousIndex() == i - 1); + T emt = li.next(); + assertTrue(content[i] == emt); + } + + assertTrue(li.nextIndex() == content.length); + assertFalse(li.hasNext()); + try { + li.next(); + failNoException(NoSuchElementException.class); + } catch (Exception e) { + checkException(NoSuchElementException.class, e); + } + + assertTrue(li.previousIndex() == (content.length - 1)); + for (int i = content.length - 1; i >= 0; i--) { + assertTrue(li.previousIndex() == i); + assertTrue(li.nextIndex() == i + 1); + assertTrue(li.hasPrevious()); + T emt = li.previous(); + assertTrue(content[i] == emt); + } + assertTrue(li.previousIndex() == -1); + assertFalse(li.hasPrevious()); + try { + li.previous(); + failNoException(NoSuchElementException.class); + } catch (Exception e) { + checkException(NoSuchElementException.class, e); + } + + assertTrue(li.nextIndex() == 0); + for (int i = 0; i < origin; i++) { + assertTrue(li.hasNext()); + assertTrue(li.nextIndex() == i); + assertTrue(content[i] == li.next()); + } + + ListIterator fbli = list.listIterator(origin); + assertTrue(fbli.nextIndex() == origin); + assertTrue(fbli.previousIndex() == (origin - 1)); + + + } + } + { + //Read/write List iteration through list contents. + for (int origin = 0; origin <= content.length; origin++) { + // start at every place + ListIterator li = list.listIterator(origin); + + for (int i = origin; i < content.length; i++) { + assertTrue(li.hasNext()); + assertTrue(li.nextIndex() == i); + assertTrue(li.previousIndex() == i - 1); + T emt = li.next(); + assertTrue(content[i] == emt); + int n = li.nextIndex(); + int p = li.previousIndex(); + li.remove(); + assertTrue(p - 1 == li.previousIndex()); + assertTrue(n - 1 == li.nextIndex()); + try { + li.remove(); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + li.add(emt); + assertTrue(p == li.previousIndex()); + assertTrue(n == li.nextIndex()); + } + + assertTrue(li.nextIndex() == content.length); + assertFalse(li.hasNext()); + try { + li.next(); + failNoException(NoSuchElementException.class); + } catch (Exception e) { + checkException(NoSuchElementException.class, e); + } + + assertTrue(li.previousIndex() == (content.length - 1)); + for (int i = content.length - 1; i >= 0; i--) { + assertTrue(li.previousIndex() == i); + assertTrue(li.nextIndex() == i + 1); + assertTrue(li.hasPrevious()); + T emt = li.previous(); + assertTrue(content[i] == emt); + int p = li.previousIndex(); + int n = li.nextIndex(); + li.remove(); + assertTrue(p == li.previousIndex()); + assertTrue(n == li.nextIndex()); + try { + li.remove(); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + li.add(emt); + assertTrue(content[i] == li.previous()); + assertTrue(p == li.previousIndex()); + assertTrue(n == li.nextIndex()); + } + assertTrue(li.previousIndex() == -1); + assertFalse(li.hasPrevious()); + try { + li.previous(); + failNoException(NoSuchElementException.class); + } catch (Exception e) { + checkException(NoSuchElementException.class, e); + } + + assertTrue(li.nextIndex() == 0); + for (int i = 0; i < origin; i++) { + assertTrue(li.hasNext()); + assertTrue(li.nextIndex() == i); + assertTrue(content[i] == li.next()); + } + + ListIterator fbli = list.listIterator(origin); + assertTrue(fbli.nextIndex() == origin); + assertTrue(fbli.previousIndex() == (origin - 1)); + + + } + + quickCheck(list, content); + } + + { + // remove all, and re-add them. + ListIterator it = list.listIterator(0); + for (int i = 0; i < content.length; i++) { + assertTrue(it.hasNext()); + assertTrue(it.next() == content[i]); + it.remove(); + } + for (int i = 0; i < content.length; i++) { + assertFalse(it.hasNext()); + it.add(content[i]); + assertTrue(it.hasPrevious()); + assertFalse(it.hasNext()); + assertTrue(content[i] == it.previous()); + assertTrue(content[i] == it.next()); + assertFalse(it.hasNext()); + } + assertFalse(it.hasNext()); + quickCheck(list, content); + + // then do it backwards. + it = list.listIterator(content.length); + for (int i = content.length - 1; i >= 0; i--) { + assertTrue(it.hasPrevious()); + assertTrue(it.previous() == content[i]); + it.remove(); + } + for (int i = content.length - 1; i >= 0; i--) { + assertFalse(it.hasPrevious()); + it.add(content[i]); + assertTrue(it.hasPrevious()); + assertTrue(content[i] == it.previous()); + assertFalse(it.hasPrevious()); + assertTrue(it.hasNext()); + } + assertFalse(it.hasPrevious()); + quickCheck(list, content); + + } + + + if (content.length > 0) { + // Check the iterator set() method. + // first forward.... + + ListIterator li = list.listIterator(); + + try { + li.set(content[0]); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + + + T tmpa = li.next(); + li.remove(); + for (int i = 1; i < content.length; i++) { + T tmpb = li.next(); + assertTrue(content[i] == tmpb); + li.set(tmpa); + tmpa = tmpb; + } + li.add(tmpa); + quickCheck(list, content); + + // then backward .... + try { + li.set(content[0]); + failNoException(IllegalStateException.class); + } catch (Exception e) { + checkException(IllegalStateException.class, e); + } + + + tmpa = li.previous(); + li.remove(); + for (int i = content.length - 2; i >= 0; i--) { + T tmpb = li.previous(); + assertTrue(content[i] == tmpb); + li.set(tmpa); + tmpa = tmpb; + } + li.add(tmpa); + quickCheck(list, content); + } + + } + + private void illegalExercise(List list, T[] content, T d, + Class eclass) { + + quickCheck(list, content); + + try { + list.add(d); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + + try { + list.addAll(Collections.singleton(d)); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + + for (int i = list.size() - 1; i >= 0; i--) { + try { + list.set(i, d); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + } + + for (int i = list.size(); i >= 0; i--) { + try { + list.add(i, d); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + + try { + list.addAll(i, Collections.singletonList(d)); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + + try { + list.addAll(i, Collections.singletonList(d)); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + } + + + quickCheck(list, content); + + ListIterator li = list.listIterator(); + for (int i = 0; i < list.size(); i++) { + assertTrue(li.hasNext()); + assertTrue(content[i] == li.next()); + try { + li.add(d); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + // The AbstractList class increments the cursor on the iterator + // even if the add fails... + // This poor accounting means that th FilterList (which does the right thing) + // can't be tested as well... Create this artificail moveTo() to + // fix the AbstractList failings. + li = moveTo(list, i); + + T prev = li.previous(); + assertTrue(content[i] == prev); + assertTrue(content[i] == li.next()); + + try { + li.set(d); + failNoException(eclass); + } catch (Exception e) { + checkException(eclass, e); + } + // The ContentList class modifies the modcount for set() + // even if the set() fails... + // This poor accounting means that the FilterList (which does the right thing) + // can't be tested as well... Create this artificail moveTo() to + // fix the AbstractList failings. + li = moveTo(list, i); + + } + + quickCheck(list, content); + + } + + private ListIterator moveTo(List list, int i) { + ListIterator li = list.listIterator(); + while (i-- >= 0) { + assertTrue(li.hasNext()); + li.next(); + } + return li; + } + + protected void quickCheck(List list, T[] content) { + // Simple iteration through the list contents. + Iterator it = list.iterator(); + int i = 0; + while (i < content.length) { + assertTrue(it.hasNext()); + assertTrue(content[i] == it.next()); + i++; + } + assertFalse(it.hasNext()); + try { + T overflow = it.next(); + fail("There should be no element after hasNext() fails, but we got: " + overflow); + } catch (NoSuchElementException nsee) { + // this is what should happen! + } + } + + + + /* ********************************* + * Test the quality of the input data + * ********************************* */ + + @Test + public void testSamples() { + for (T s : buildSampleContent()) { + if (s != null && !i_tclass.isInstance(s)) { + fail("We expect all sample data to be an instance of " + i_tclass.getName()); + } + } + } + + @Test + public void testIllegalClassData() { + for (Object o : buildIllegalClassContent()) { + if (o == null || i_tclass.isInstance(o)) { + fail("We expect all IllegalClass data to be something other than " + i_tclass.getName()); + } + } + } + + @Test + public void testIllegalArgumentData() { + for (T s : buildIllegalArgumentContent()) { + if (s != null && !i_tclass.isInstance(s)) { + fail("We expect all IllegalArgument data to be an instance of " + i_tclass.getName()); + } + } + } + + /* ********************************* + * The actual tests to run. + * ********************************* */ + + @Test + public void testToString() { + // basic run + assertTrue(buildEmptyList().toString() != null); + } + + @Test + public void testEmpty() { + List empty = buildEmptyList(); + assertTrue(empty.size() == 0); + assertTrue(empty.isEmpty()); + exercise(empty, buildArray(0)); + } + + @Test + public void testAdd() { + for (T s : buildSampleContent()) { + List list = buildEmptyList(); + assertTrue(list.isEmpty()); + assertTrue(list.add(s)); + exercise(list, s); + } + List list = buildEmptyList(); + T[] content = buildSampleContent(); + for (T s : content) { + assertTrue(list.add(s)); + } + exercise(list, content); + } + + @Test + public void testAddAll() { + for (T s : buildSampleContent()) { + List list = buildEmptyList(); + assertTrue(list.isEmpty()); + assertTrue(list.addAll(Arrays.asList(s))); + exercise(list, s); + list.clear(); + } + List list = buildEmptyList(); + T[] content = buildSampleContent(); + assertTrue(list.addAll(Arrays.asList(content))); + exercise(list, content); + T[] data = buildArray(0); + assertFalse(list.addAll(Arrays.asList(data))); + } + + @Test + public void testAddAllInt() { + for (T s : buildSampleContent()) { + List list = buildEmptyList(); + assertTrue(list.isEmpty()); + assertTrue(list.addAll(0, Arrays.asList(s))); + exercise(list, s); + list.clear(); + } + List list = buildEmptyList(); + T[] content = buildSampleContent(); + assertTrue(list.addAll(0, Arrays.asList(content))); + exercise(list, content); + T[] data = buildArray(0); + assertFalse(list.addAll(0, Arrays.asList(data))); + } + + @Test + public void testIllegalAddAllInt() { + final T[] illegal = buildIllegalArgumentContent(); + if (illegal.length <= 0) { + // for android. + //Assume.assumeTrue(illegal.length > 0); + return; + } + final T[] extra = buildAdditionalContent(); + if (extra.length <= 0) { + // for android. + //Assume.assumeTrue(extra.length > 0); + return; + } + final T[] content = buildSampleContent(); + if (content.length <= 0) { + // for android. + //Assume.assumeTrue(content.length > 0); + return; + } + T[] toadd = ArrayCopy.copyOf(extra, extra.length + illegal.length); + System.arraycopy(illegal, 0, toadd, extra.length, illegal.length); + + // right, we have legal content in 'content', and then in 'illegal' we + // have some legal content, and then some illegal content. + + // populate the list. + List list = buildEmptyList(); + assertTrue(list.addAll(0, Arrays.asList(content))); + quickCheck(list, content); + + // check that the first to-add can be added. + list.add(0, toadd[0]); + //then remove it again. + assertTrue(toadd[0] == list.remove(0)); + + for (int i = 0; i <= content.length; i++) { + + quickCheck(list, content); + + // now, add the illegal, and then inspect the list... + try { + list.addAll(i, Arrays.asList(toadd)); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + + // make sure that the member that previously could be added can + // still be added. + list.add(0, toadd[0]); + //then remove it again. + assertTrue(toadd[0] == list.remove(0)); + + // make sure it's all OK. + exercise(list, content); + + if (content.length < 2) { + // for android. + // Assume.assumeTrue(content.length >= 2); + return; + } + + // now check to make sure that concurrency is not affected.... + Iterator it = list.iterator(); + // move it along at least once..... + assertTrue(content[0] == it.next()); + // now do a failed addAll. + try { + list.addAll(i, Arrays.asList(toadd)); + failNoException(IllegalArgumentException.class); + } catch (Exception e) { + checkException(IllegalArgumentException.class, e); + } + // we should be able to move the iteratoe because the modcount should + // not have been affected..... + assertTrue(content[1] == it.next()); + + + + } + + } + + @Test + public void testClear() { + List list = buildEmptyList(); + assertTrue(list.addAll(Arrays.asList(buildSampleContent()))); + assertFalse(list.isEmpty()); + list.clear(); + assertTrue(list.isEmpty()); + } + + @Test + public void testInsert() { + for (T s : buildSampleContent()) { + List list = buildEmptyList(); + assertTrue(list.isEmpty()); + list.add(0, s); + exercise(list, s); + } + List list = buildEmptyList(); + T[] content = buildSampleContent(); + for (T s : content) { + list.add(0, s); + } + exercise(list, arrayReverse(content)); + list.clear(); + + int[] order = shuffle(content.length); + T[] mix = buildArray(order.length); + for (int i = 0; i < order.length; i++) { + mix[i] = content[order[i]]; + } + for (int i = 0; i < content.length; i++) { + int pos = 0; + for (int j = 0; j < order.length; j++) { + if (order[j] < i) { + pos++; + } + if (order[j] == i) { + break; + } + } + list.add(pos, content[i]); + } + exercise(list, mix); + + } + + @Test + public void testNullAdd() { + if (i_nullok) { + List list = buildEmptyList(); + assertTrue(list.add(null)); + assertTrue(list.get(0) == null); + list.add(1, null); + assertTrue(list.get(1) == null); + assertTrue(list.addAll(Arrays.asList(buildArray(10)))); + assertTrue(list.size() == 12); + } else { + try { + List list = buildEmptyList(); + assertFalse(list.add(null)); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + try { + List list = buildEmptyList(); + list.add(0, null); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + + try { + List list = buildEmptyList(); + T[] data = buildSampleContent(); + for (int i = 0; i < data.length; i+= 2) { + list.add(data[i]); + data[i] = null; + } + if (data.length == 1) { + data[0] = null; + } + list.addAll(0, Arrays.asList(data)); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + + try { + List list = buildEmptyList(); + T[] data = buildSampleContent(); + for (int i = 0; i < data.length; i+= 2) { + list.add(data[i]); + data[i] = null; + } + if (data.length == 1) { + data[0] = null; + } + list.addAll(Arrays.asList(data)); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + + + } + + try { + List list = buildEmptyList(); + Collection data = null; + list.addAll(0, data); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + + try { + List list = buildEmptyList(); + Collection data = null; + list.addAll(Arrays.asList(buildSampleContent())); + list.addAll(data); + failNoException(NullPointerException.class); + } catch (Exception e) { + checkException(NullPointerException.class, e); + } + } + + @Test + public void testIllegalIndex() { + T[] data = buildSampleContent(); + try { + List list = buildEmptyList(); + list.add(-10, data[0]); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.add(1, data[0]); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.addAll(-10, Arrays.asList(data)); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.addAll(1, Arrays.asList(data)); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.set(-1, data[0]); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.set(1, data[0]); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.get(-1); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.get(1); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + + try { + List list = buildEmptyList(); + list.remove(-10); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + try { + List list = buildEmptyList(); + list.remove(1); + failNoException(IndexOutOfBoundsException.class); + } catch (Exception e) { + checkException(IndexOutOfBoundsException.class, e); + } + } + + @Test + public void testRemove() { + for (T s : buildSampleContent()) { + List list = buildEmptyList(); + assertTrue(list.isEmpty()); + assertTrue(list.add(s)); + exercise(list, s); + assertTrue(list.remove(0) == s); + exercise(list); + } + T[] samplecontent = buildSampleContent(); + for (int i = 0; i < samplecontent.length; i++) { + T[] content = samplecontent; + List list = buildEmptyList(); + for (T s : content) { + assertTrue(list.add(s)); + } + exercise(list, content); + assertTrue(content[i] == list.remove(i)); + content = arrayRemove(content, i); + exercise(list, content); + + while (list.size() > i) { + assertTrue(content[i] == list.remove(i)); + content = arrayRemove(content, i); + } + while (!list.isEmpty()) { + assertTrue(content[content.length -1] == list.remove(list.size() - 1)); + content = arrayRemove(content, content.length - 1); + } + assertTrue(list.isEmpty()); + } + List list = buildEmptyList(); + for (T s : samplecontent) { + assertTrue(list.add(s)); + } + exercise(list, samplecontent); + if (i_nullok) { + list = buildEmptyList(); + assertTrue(list.add(null)); + assertTrue(list.get(0) == null); + } + } + + + @Test + public void testIllegalArgumentContent() { + for (T d : buildIllegalArgumentContent()) { + List list = buildEmptyList(); + T[] content = buildSampleContent(); + for (T i : content) { + list.add(i); + } + illegalExercise(list, content, d, IllegalArgumentException.class); + } + } + + @Test + public void testIllegalClassContent() { + for (Object d : buildIllegalClassContent()) { + List list = buildEmptyList(); + T[] content = buildSampleContent(); + for (T i : content) { + list.add(i); + } + illegalExercise(list, content, (T)d, ClassCastException.class); + } + } + + @Test + public void testNullContent() { + if (i_nullok) { + return; + } + List list = buildEmptyList(); + T[] content = buildSampleContent(); + for (T i : content) { + list.add(i); + } + illegalExercise(list, content, (T)null, NullPointerException.class); + } + + + @Test + public void testConcurrentMod() { + List list = buildEmptyList(); + + T[] sample = buildSampleContent(); + assertTrue("Not enough sample data " + sample.length, sample.length > 2); + + list.add(sample[0]); + + ListIterator it = list.listIterator(); + Iterator si = list.iterator(); + + // It is important to add some content, and do a next() + // before testing concurrency, because some iterator methods + // check for the Iterator state before checking concurrency. + // this puts the iterator in to a valid state for remove(), set(), etc. + assertTrue(sample[0] == it.next()); + assertTrue(sample[0] == si.next()); + + // create a concurrent issue. + // we have checked for more than 2 elements so this should be fine. + list.add(sample[1]); + list.add(sample[2]); + + // hasNext() should never throw CME. + assertTrue(it.hasNext()); + assertTrue(1 == it.nextIndex()); + + // hasNext() should never throw CME. + assertTrue(it.hasNext()); + try { + it.next(); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + + it = list.listIterator(3); + assertTrue(sample[2] == it.previous()); + + // create a concurrent issue. + list.remove(2); + + // hasPrevious() should never throw CME. + assertTrue(it.hasPrevious()); + + assertTrue(1 == it.previousIndex()); + + // hasPrevious() should never throw CME. + assertTrue(it.hasPrevious()); + try { + it.previous(); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + // hasPrevious() should never throw CME. + assertTrue(it.hasPrevious()); + try { + it.set(sample[sample.length - 1]); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + // hasPrevious() should never throw CME. + assertTrue(it.hasPrevious()); + try { + it.add(sample[sample.length - 1]); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + // hasPrevious() should never throw CME. + assertTrue(it.hasPrevious()); + try { + it.remove(); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + + + // Now test the Simple Iterator concurrency + + // hasNext() should never throw CME. + assertTrue(si.hasNext()); + + try { + si.next(); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + try { + si.remove(); + failNoException(ConcurrentModificationException.class); + } catch (Exception e) { + checkException(ConcurrentModificationException.class, e); + } + + } + + @Test + public void testConcurrentSetMod() { + List list = buildEmptyList(); + + T[] sample = buildSampleContent(); + assertTrue("Not enough sample data " + sample.length, sample.length > 2); + + list.addAll(Arrays.asList(sample)); + quickCheck(list, sample); + + T tmp = list.remove(0); + + T[] tmpsamp = ArrayCopy.copyOfRange(sample, 1, sample.length); + quickCheck(list, tmpsamp); + + // get a handle on our iterator.... + ListIterator it = list.listIterator(); + + // make sure a set of the underlying does not effect the iterator. + tmpsamp[0] = tmp; + list.set(0, tmp); + quickCheck(list, tmpsamp); + + // next() should not throw CME because set() should not change modCount. + assertTrue(tmp == it.next()); + + // then, for kicks, restore the original list with + it.add(sample[1]); + quickCheck(list, sample); + } + + +} diff --git a/test/src/java/org/jdom/test/util/FidoFetch.java b/test/src/java/org/jdom/test/util/FidoFetch.java new file mode 100644 index 0000000..5d05c45 --- /dev/null +++ b/test/src/java/org/jdom/test/util/FidoFetch.java @@ -0,0 +1,53 @@ +package org.jdom.test.util; + +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.atomic.AtomicReference; + +/** + * Android does not have a reliable ClassLoader.getResource() set of methods. + * We do a poor-man's hack that works on Android too. + * On Android, we need to make a new class org.jdom.test.util.AndroidFetch. + * + * @author Rolf Lear + * + */ + +public class FidoFetch { + + /** + * @param name The resource name to get. + * @return The URL to the name. + */ + public URL getURL(String name) { + return this.getClass().getResource(name); + } + + /** + * @param name The resource name to get. + * @return The Resource as a stream + */ + public InputStream getStream(String name) { + return this.getClass().getResourceAsStream(name); + } + + private static final AtomicReference fetch = new AtomicReference(); + + /** + * A singleton instance type method. + * @return the singleton Fido instance. + */ + public static final FidoFetch getFido() { + FidoFetch ret = fetch.get(); + if (ret == null) { +// if ("Dalvik".equalsIgnoreCase(System.getProperty("java.vm.name", "junk"))) { +// ret = ReflectionConstructor.construct("org.jdom.test.util.AndroidFetch", FidoFetch.class); +// } else { + ret = new FidoFetch(); +// } + fetch.set(ret); + } + return ret; + } + +} diff --git a/test/src/java/org/jdom/test/util/ListTest.java b/test/src/java/org/jdom/test/util/ListTest.java new file mode 100644 index 0000000..4ad1f58 --- /dev/null +++ b/test/src/java/org/jdom/test/util/ListTest.java @@ -0,0 +1,38 @@ +package org.jdom.test.util; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("javadoc") +public final class ListTest extends AbstractTestList { + + public ListTest() { + super(Integer.class, true); + } + + @Override + public List buildEmptyList() { + return new ArrayList(); + } + + @Override + public Integer[] buildSampleContent() { + return new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + } + + @Override + public Object[] buildIllegalClassContent() { + return new Object[]{}; + } + + @Override + public Integer[] buildIllegalArgumentContent() { + return new Integer[0]; + } + + @Override + public Integer[] buildAdditionalContent() { + return new Integer[0]; + } + +} \ No newline at end of file diff --git a/test/src/java/org/jdom/test/util/UnitTestUtil.java b/test/src/java/org/jdom/test/util/UnitTestUtil.java new file mode 100644 index 0000000..6dc87ca --- /dev/null +++ b/test/src/java/org/jdom/test/util/UnitTestUtil.java @@ -0,0 +1,443 @@ +package org.jdom.test.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.jdom.Attribute; +import org.jdom.Comment; +import org.jdom.Content; +import org.jdom.DocType; +import org.jdom.Document; +import org.jdom.Element; +import org.jdom.EntityRef; +import org.jdom.Namespace; +import org.jdom.NamespaceAware; +import org.jdom.ProcessingInstruction; + +@SuppressWarnings("javadoc") +public class UnitTestUtil { + + private static final AtomicBoolean isandroid = new AtomicBoolean(false); + + public static final void setAndroid() { + isandroid.set(true); + } + + public static final void testNamespaceIntro(NamespaceAware content, Namespace...expect) { + List intro = content.getNamespacesIntroduced(); + List exp = Arrays.asList(expect); + + if (!exp.equals(intro)) { + fail("We expect Introduced equal the Expected Namespaces." + + "\n Introduced: " + intro.toString() + + "\n Expected: " + exp.toString()); + } + } + + public static final void testNamespaceScope(NamespaceAware content, Namespace...expect) { + List introduced = content.getNamespacesIntroduced(); + List inscope = new ArrayList(content.getNamespacesInScope()); + List inherited = content.getNamespacesInherited(); + + if (introduced.size() + inherited.size() != inscope.size()) { + fail(String.format("InScope Namespaces do not add up: InScope=%d Inherited=%d, Introduced=%d", + inscope.size(), inherited.size(), introduced.size())); + } + int inher = 0; + int intro = 0; + for (Namespace ns : inscope) { + if (inher < inherited.size() && inherited.get(inher) == ns) { + inher++; + } + if (intro < introduced.size() && introduced.get(intro) == ns) { + intro++; + } + } + if (intro < introduced.size()) { + // we are missing content(or it is out of order) in the lists. + fail("Introduced list contains out-of-order, or non-intersecting Namespaces." + + "\n InScope: " + inscope.toString() + + "\n Introduced: " + introduced.toString()); + } + if (inher < inherited.size()) { + // we are missing content(or it is out of order) in the lists. + fail("Inherited list contains out-of-order, or non-intersecting Namespaces." + + "\n InScope: " + inscope.toString() + + "\n Inherited: " + inherited.toString()); + } + + + int i = 0; + for (Namespace ns : inscope) { + if (i >= expect.length) { + fail("We have additional Namespaces " + inscope.subList(i, inscope.size())); + } + if (expect[i] != ns) { + assertEquals("InScope differs from Expected at index " + i, expect[i], ns); + } + i++; + } + if (i < expect.length) { + ArrayList missing = new ArrayList(expect.length - i); + while (i < expect.length) { + missing.add(expect[i++]); + } + fail("InScope is missing Namespaces " + missing); + } + + inscope.removeAll(introduced); + if (!inscope.equals(inherited)) { + fail("We expect InScope - Introduced to equal Inherited." + + "\n InScope: " + inscope.toString() + + "\n Inherited: " + inherited.toString()); + } + + } + + /** + * Serialize, then deserialize an instance object. + * @param tclass + * @param input + * @return + */ + public static final T deSerialize(final T input) { + if (input == null) { + return null; + } + final Class tclass = input.getClass(); + final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + final ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream); + try { + objectOutputStream.writeObject(input); + } + catch(final IOException ioException) { + failException("Unable to serialize object.", ioException); + } + finally { + try { + objectOutputStream.close(); + } + catch(final IOException ioException) { + failException("failed to close object stream while serializing object", ioException); + } + } + } + catch(final IOException ioException) { + failException("unable to serialize object", ioException); + } + finally { + try { + outputStream.close(); + } + catch(final IOException ioException) { + failException("failed to close output stream while serializing object", ioException); + } + } + + final byte[] bytes = outputStream.toByteArray(); + + Object o = null; + + final InputStream inputStream = new ByteArrayInputStream(bytes); + try { + final ObjectInputStream objectInputStream = new ObjectInputStream(inputStream); + try { + o = objectInputStream.readObject(); + } + finally { + try { + objectInputStream.close(); + } + catch(final IOException ioException) { + failException("failed to close object stream while deserializing object", ioException); + } + } + } + catch(final IOException ioException) { + failException("unable to deserialize object.", ioException); + } + catch(final ClassNotFoundException classNotFoundException) { + failException("unable to deserialize object.", classNotFoundException); + } + finally { + try{ + inputStream.close(); + } + catch(final IOException ioException) { + failException("failed to close output stream while serializing object.", ioException); + } + } + + assertTrue("Deserialized (non-null) object is null!", o != null); + + @SuppressWarnings("unchecked") + final T t = (T)tclass.cast(o); + + assertTrue("Deserialized instance should not be the same instance as the pre-serialization", + t != input); + + return t; + + } + + /** + * Attribute order is not significant in XML documents. + * This method re-arranges the order of the Attributes (in somewhat + * alphabetical order, but that's not important) so that you can easily + * compare the attribute content of two different elements. + * @param emt The element who's Attributes we should rearrange. + */ + public static final void normalizeAttributes(Element emt) { + if (!emt.hasAttributes()) { + return; + } + emt.sortAttributes(new Comparator() { + @Override + public int compare(Attribute o1, Attribute o2) { + return o1.getQualifiedName().compareTo(o2.getQualifiedName()); + } + }); + for (Object o : emt.getChildren()) { + normalizeAttributes((Element)o); + } + } + + + /** + * Test whether two values are equals, in addition, check the hashCode() values + * This method will pass if both values are null. + * @param a The first value to check + * @param b The other value + */ + public static final void checkEquals(Object a, Object b) { + if (a == null) { + if (b != null) { + fail("Second value is not null like the first: " + b.toString()); + } + return; + } + if (b == null) { + fail ("First value is not null like the second: " + a.toString()); + } + assertTrue(a.equals(a)); + assertTrue(b.equals(b)); + if (!a.equals(b)) { + fail("First value '" + a + "' does not equals() second value '" + b + "'."); + } + if (!b.equals(a)) { + fail("Second value '" + b + "' does not equals() first value '" + a + "'."); + } + if (a.hashCode() != b.hashCode()) { + fail("Hashcodes of equals() values are different. " + + "First value '" + a + "' (hashcode=" + a.hashCode() + ") " + + "not same as " + + "second value '" + b + "' (hashcode=" + b.hashCode() + ")."); + } + } + + public static final void testReadIterator(Iterator it, Object ... values) { + assertTrue(it != null); + assertTrue(values != null); + for (int i = 0; i < values.length; i++) { + assertTrue(it.hasNext()); + assertTrue(values[i] == it.next()); + } + assertFalse("Not enough values in the test set.", it.hasNext()); + try { + it.next(); + fail("Should not be able to have next after values have run out."); + } catch (NoSuchElementException nsee) { + // the way it should be + } catch (Exception e) { + fail("next() should fail with NoSuchElementException, not " + e.getClass().getName()); + } + + } + + /** + * Create a new String instance based on an input String. + * The String class tends to have these fancy ways of re-using internal + * data structures when creating one string from another. This method + * circumvents those optimisations. + * @param value The value to clone + * @return The cloned version of the input value. + */ + public static final String cloneString(String value) { + char[] chars = value.toCharArray(); + char[] ret = new char[chars.length]; + System.arraycopy(chars, 0, ret, 0, chars.length); + return new String(ret); + } + + /** + * Provide a standard way to fail when we expect an exception but did not + * get one... + */ + public static final void failNoException(Class expect) { + fail("This code was expected to produce the exception " + + expect.getName() + " but it was not thrown."); + } + + /** + * Provide a standard test for checking for an exception. + * @param expect + * @param got + */ + public static final void checkException(Class expect, + Throwable got) { + if (expect == null) { + fail("We can't expect a null exception (cryptic enough?)"); + } + if (got == null) { + fail("We expected an exception of type " + expect.getName() + + " but got a null value instead"); + } + if (!expect.isInstance(got)) { + AssertionError ae = new AssertionError( + "We expected an exception of type " + expect.getName() + + " but got a " + got.getClass().getName() + + " instead with message " + got.getMessage()); + if (isandroid.get()) { + System.out.println("ANDROID: About to throw AssertionError " + + "but we cannot initialize the cause... " + + "Here follows both the AssertionError and its cause"); + ae.printStackTrace(); + System.out.println("And the cause..."); + got.printStackTrace(); + } else { + ae.initCause(got); + } + throw ae; + } + } + + /** + * Call this if you have an exception you want to fail for! + * @param message + * @param got this exception + */ + public static void failException(String message, Throwable got) { + AssertionError ae = new AssertionError(message); + if (isandroid.get()) { + System.out.println("ANDROID: About to throw AssertionError " + + "but we cannot initialize the cause... " + + "Here follows both the AssertionError and its cause"); + ae.printStackTrace(); + System.out.println("And the cause..."); + got.printStackTrace(); + } else { + ae.initCause(got); + } + throw ae; + } + + public static final void compare(final NamespaceAware ap, final NamespaceAware bp) { + assertNotNull(ap); + assertNotNull(bp); + assertEquals(ap.getClass(), bp.getClass()); + assertTrue(ap != bp); + if (ap instanceof Content) { + final Content a = (Content)ap; + final Content b = (Content)bp; + if (a.getParent() != null) { + assertTrue(a.getParent() != b.getParent()); + } + + switch (a.getCType()) { + case Text: + assertEquals(a.getValue(), b.getValue()); + break; + case CDATA: + assertEquals(a.getValue(), b.getValue()); + break; + case Comment: + assertEquals(((Comment)a).getText(), ((Comment)b).getText()); + break; + case DocType: + DocType da = (DocType)a; + DocType db = (DocType)b; + assertEquals(da.getElementName(), db.getElementName()); + assertEquals(da.getPublicID(), db.getPublicID()); + assertEquals(da.getSystemID(), db.getSystemID()); + assertEquals(da.getInternalSubset(), db.getInternalSubset()); + break; + case Element: + Element ea = (Element)a; + Element eb = (Element)b; + assertEquals(ea.getName(), eb.getName()); + compare(ea.getAttributes(), eb.getAttributes()); + assertEquals(ea.getNamespacesInScope(), eb.getNamespacesInScope()); + final int sz = ea.getContentSize(); + assertTrue(sz == eb.getContentSize()); + for (int i = 0; i < sz; i++) { + compare(ea.getContent(i), eb.getContent(i)); + } + break; + case EntityRef: + assertEquals(((EntityRef)a).getName(), ((EntityRef)b).getName()); + break; + case ProcessingInstruction: + ProcessingInstruction pa = (ProcessingInstruction)a; + ProcessingInstruction pb = (ProcessingInstruction)b; + assertEquals(pa.getTarget(), pb.getTarget()); + assertEquals(pa.getData(), pb.getData()); + break; + } + } else if (ap instanceof Attribute) { + compare ((Attribute)ap, (Attribute)bp); + } else if (ap instanceof Document) { + Document a = (Document)ap; + Document b = (Document)bp; + assertEquals(a.getBaseURI(), b.getBaseURI()); + final int sz = a.getContentSize(); + assertTrue(sz == b.getContentSize()); + for (int i = 0; i < sz; i++) { + compare(a.getContent(i), b.getContent(i)); + } + } + } + + + public static final void compare(List a, List b) { + assertTrue(a != b); + assertTrue(a.size() == b.size()); + Iterator ait = a.iterator(); + Iterator bit = b.iterator(); + while (ait.hasNext() && bit.hasNext()) { + Attribute aa = ait.next(); + Attribute bb = bit.next(); + compare(aa, bb); + } + assertFalse(ait.hasNext()); + assertFalse(bit.hasNext()); + + } + + + public static final void compare(Attribute aa, Attribute bb) { + assertEquals(aa.getName(), bb.getName()); + assertTrue(aa.getNamespace() == bb.getNamespace()); + assertEquals(aa.getValue(), bb.getValue()); + } + + +} diff --git a/test/src/java/org/jdom/test/util/VerifierTestBuilder.java b/test/src/java/org/jdom/test/util/VerifierTestBuilder.java new file mode 100644 index 0000000..4842b10 --- /dev/null +++ b/test/src/java/org/jdom/test/util/VerifierTestBuilder.java @@ -0,0 +1,124 @@ +package org.jdom.test.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; + +import org.jdom.Verifier; +import org.jdom.internal.ArrayCopy; + +/** + * This class builds jUnitTestCases for the Verifier character classes. + * It is based on the JDOM 1.1.1 verifier. Whatever that verifier accepted, + * this one will too. In other words, it is for regression purposes only, not + * reference. + * @author Rolf Lear + * + */ +public class VerifierTestBuilder { + + /** + * @param args + * @throws InvocationTargetException + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + Class vclass = Verifier.class; + + // Hunt for all Verifier methods that are of the form static boolean ...(char) + for (Method meth : vclass.getMethods()) { + if (meth.getReturnType() != Boolean.TYPE) { + // we only want methods that return boolean. + continue; + } + Type[] types = meth.getParameterTypes(); + if (types.length != 1 || types[0] != Character.TYPE) { + // we want methods that take a single char argument + continue; + } + if (!Modifier.isPublic(meth.getModifiers()) || !Modifier.isStatic(meth.getModifiers())) { + // we want the public static methods only + continue; + } + + String mname = meth.getName(); + + boolean valid = false; + boolean q = false; + int[] flips = new int[1024]; + int flen = 0; + + for (char i = 0; i < Character.MAX_VALUE; i++) { + q = (Boolean)meth.invoke(null, Character.valueOf(i)); + if (q != valid) { + valid = q; + if (flen >= flips.length) { + flips = ArrayCopy.copyOf(flips, flen + 1024); + } + flips[flen++] = i; + } + } + + buildTest(mname, ArrayCopy.copyOf(flips, flen)); + } + } + + private static final void buildTest(final String mname, int[] flips) { + + final String tname = "test" + mname.substring(0, 1).toUpperCase() + mname.substring(1); + + StringBuilder sb = new StringBuilder(); + appendLine(0, sb, ""); + appendLine(1, sb, "// Automated test built by VerifierTestBuilder"); + appendLine(1, sb, "@Test"); + + appendLine(1, sb, "public void ", tname ,"() {"); + appendLine(2, sb, "final int[] flips = new int[] {"); + for (int i = 0; i < flips.length; i++) { + if ((i & 0x0f) == 0) { + sb.append("\n\t\t\t\t"); + } + sb.append(String.format(" 0x%04x,", flips[i])); + } + sb.setCharAt(sb.length() - 1, '\n'); + appendLine(2, sb, "};"); + appendLine(2, sb, "int c = 0;"); + appendLine(2, sb, "int fcnt = 0;"); + appendLine(2, sb, "boolean valid = false;"); + appendLine(2, sb, "final long ms = System.currentTimeMillis();"); + + appendLine(2, sb, "while (c <= Character.MAX_VALUE) {"); + appendLine(3, sb, "if (fcnt < flips.length && flips[fcnt] == c) {"); + appendLine(4, sb, "valid = !valid;"); + appendLine(4, sb, "fcnt++;"); + appendLine(3, sb, "}"); + appendLine(3, sb, "if (valid) {"); + appendLine(4, sb, "if (!Verifier." + mname + "((char)c)) {"); + appendLine(5, sb, "fail(\"Expected char 0x\" + Integer.toHexString(c) + \" to pass " + mname + " but it failed.\");"); + appendLine(4, sb, "}"); + appendLine(3, sb, "} else {"); + appendLine(4, sb, "if (Verifier." + mname + "((char)c)) {"); + appendLine(5, sb, "fail(\"Expected char 0x\" + Integer.toHexString(c) + \" to fail " + mname + " but it passed.\");"); + appendLine(4, sb, "}"); + appendLine(3, sb, "}"); + appendLine(3, sb, "c++;"); + appendLine(2, sb, "}"); + appendLine(2, sb, "System.out.printf(\"Completed test", tname, "in %dms\\n\", System.currentTimeMillis() - ms);"); + + appendLine(1, sb, "}"); + System.out.println(sb); + } + + private static final void appendLine(int indent, StringBuilder sb, String...vals) { + while (--indent >= 0) { + sb.append("\t"); + } + for (String s : vals) { + sb.append(s).append(" "); + } + sb.setCharAt(sb.length() - 1, '\n'); + } + +} diff --git a/test/src/resources/DOMBuilder/attributes.xml b/test/src/resources/DOMBuilder/attributes.xml new file mode 100644 index 0000000..53098e0 --- /dev/null +++ b/test/src/resources/DOMBuilder/attributes.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/src/resources/DOMBuilder/attributesandchildren.xml b/test/src/resources/DOMBuilder/attributesandchildren.xml new file mode 100644 index 0000000..5b0e7d3 --- /dev/null +++ b/test/src/resources/DOMBuilder/attributesandchildren.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/src/resources/DOMBuilder/complex.xml b/test/src/resources/DOMBuilder/complex.xml new file mode 100644 index 0000000..e131811 --- /dev/null +++ b/test/src/resources/DOMBuilder/complex.xml @@ -0,0 +1,21 @@ + + + + + + + text + hello Frodo Baggins! + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/resources/DOMBuilder/doctype.xml b/test/src/resources/DOMBuilder/doctype.xml new file mode 100644 index 0000000..a464464 --- /dev/null +++ b/test/src/resources/DOMBuilder/doctype.xml @@ -0,0 +1,3 @@ + +]> +&xpd; \ No newline at end of file diff --git a/test/src/resources/DOMBuilder/doctypesimple.xml b/test/src/resources/DOMBuilder/doctypesimple.xml new file mode 100644 index 0000000..c95a0a2 --- /dev/null +++ b/test/src/resources/DOMBuilder/doctypesimple.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/test/src/resources/DOMBuilder/namespaces.xml b/test/src/resources/DOMBuilder/namespaces.xml new file mode 100644 index 0000000..61ecddc --- /dev/null +++ b/test/src/resources/DOMBuilder/namespaces.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/test/src/resources/DOMBuilder/simple.xml b/test/src/resources/DOMBuilder/simple.xml new file mode 100644 index 0000000..635b93e --- /dev/null +++ b/test/src/resources/DOMBuilder/simple.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/src/resources/SAXBuilderTestDecl.dtd b/test/src/resources/SAXBuilderTestDecl.dtd new file mode 100644 index 0000000..21dbef5 --- /dev/null +++ b/test/src/resources/SAXBuilderTestDecl.dtd @@ -0,0 +1,10 @@ + + + + + + + + ' > + + diff --git a/test/src/resources/SAXBuilderTestDecl.xml b/test/src/resources/SAXBuilderTestDecl.xml new file mode 100644 index 0000000..09fa8df --- /dev/null +++ b/test/src/resources/SAXBuilderTestDecl.xml @@ -0,0 +1,15 @@ + + + + + + + + + + +]> + +&simple; &simple2; + diff --git a/test/src/resources/SAXBuilderTestEntity.dtd b/test/src/resources/SAXBuilderTestEntity.dtd new file mode 100644 index 0000000..75b6ee2 --- /dev/null +++ b/test/src/resources/SAXBuilderTestEntity.dtd @@ -0,0 +1,7 @@ + + + + + + diff --git a/test/src/resources/SAXBuilderTestEntity.xml b/test/src/resources/SAXBuilderTestEntity.xml new file mode 100644 index 0000000..0c00c3e --- /dev/null +++ b/test/src/resources/SAXBuilderTestEntity.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + +]> + +&simple; &simple2; + diff --git a/test/src/resources/SAXBuilderTestEntity2.xml b/test/src/resources/SAXBuilderTestEntity2.xml new file mode 100644 index 0000000..eaa8181 --- /dev/null +++ b/test/src/resources/SAXBuilderTestEntity2.xml @@ -0,0 +1,5 @@ + + + +&simple; &simple2; + diff --git a/test/src/resources/SAXBuilderTestIntExtEntity.xml b/test/src/resources/SAXBuilderTestIntExtEntity.xml new file mode 100644 index 0000000..23da794 --- /dev/null +++ b/test/src/resources/SAXBuilderTestIntExtEntity.xml @@ -0,0 +1,13 @@ + + + + +] +> +” + + MainIndex + “ + diff --git a/test/src/resources/complex.xml b/test/src/resources/complex.xml new file mode 100644 index 0000000..c9e7b91 --- /dev/null +++ b/test/src/resources/complex.xml @@ -0,0 +1,26 @@ + + ] > + + text + hello Frodo Baggins! + + + + + + + + + + + + + &unres; some unresolved content. + + + + + + + + \ No newline at end of file diff --git a/test/src/resources/test.dtd b/test/src/resources/test.dtd new file mode 100644 index 0000000..4898705 --- /dev/null +++ b/test/src/resources/test.dtd @@ -0,0 +1,6 @@ + + + + + + diff --git a/test/src/resources/xmlchars.xml b/test/src/resources/xmlchars.xml new file mode 100644 index 0000000..6ad79ab --- /dev/null +++ b/test/src/resources/xmlchars.xml @@ -0,0 +1,105 @@ + + + + +Char#x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + + + +LetterBaseChar | Ideographic + + +BaseChar[#x0041-#x005A] |[#x0061-#x007A] |[#x00C0-#x00D6] +|[#x00D8-#x00F6] |[#x00F8-#x00FF] |[#x0100-#x0131] |[#x0134-#x013E] +|[#x0141-#x0148] |[#x014A-#x017E] |[#x0180-#x01C3] |[#x01CD-#x01F0] +|[#x01F4-#x01F5] |[#x01FA-#x0217] |[#x0250-#x02A8] |[#x02BB-#x02C1] +|#x0386 |[#x0388-#x038A] |#x038C |[#x038E-#x03A1] +|[#x03A3-#x03CE] |[#x03D0-#x03D6] |#x03DA |#x03DC +|#x03DE |#x03E0 |[#x03E2-#x03F3] |[#x0401-#x040C] +|[#x040E-#x044F] |[#x0451-#x045C] |[#x045E-#x0481] |[#x0490-#x04C4] +|[#x04C7-#x04C8] |[#x04CB-#x04CC] |[#x04D0-#x04EB] |[#x04EE-#x04F5] +|[#x04F8-#x04F9] |[#x0531-#x0556] |#x0559 |[#x0561-#x0586] +|[#x05D0-#x05EA] |[#x05F0-#x05F2] |[#x0621-#x063A] |[#x0641-#x064A] +|[#x0671-#x06B7] |[#x06BA-#x06BE] |[#x06C0-#x06CE] |[#x06D0-#x06D3] +|#x06D5 |[#x06E5-#x06E6] |[#x0905-#x0939] |#x093D +|[#x0958-#x0961] |[#x0985-#x098C] |[#x098F-#x0990] |[#x0993-#x09A8] +|[#x09AA-#x09B0] |#x09B2 |[#x09B6-#x09B9] |[#x09DC-#x09DD] +|[#x09DF-#x09E1] |[#x09F0-#x09F1] |[#x0A05-#x0A0A] |[#x0A0F-#x0A10] +|[#x0A13-#x0A28] |[#x0A2A-#x0A30] |[#x0A32-#x0A33] |[#x0A35-#x0A36] +|[#x0A38-#x0A39] |[#x0A59-#x0A5C] |#x0A5E |[#x0A72-#x0A74] +|[#x0A85-#x0A8B] |#x0A8D |[#x0A8F-#x0A91] |[#x0A93-#x0AA8] +|[#x0AAA-#x0AB0] |[#x0AB2-#x0AB3] |[#x0AB5-#x0AB9] |#x0ABD +|#x0AE0 |[#x0B05-#x0B0C] |[#x0B0F-#x0B10] |[#x0B13-#x0B28] +|[#x0B2A-#x0B30] |[#x0B32-#x0B33] |[#x0B36-#x0B39] |#x0B3D +|[#x0B5C-#x0B5D] |[#x0B5F-#x0B61] |[#x0B85-#x0B8A] |[#x0B8E-#x0B90] +|[#x0B92-#x0B95] |[#x0B99-#x0B9A] |#x0B9C |[#x0B9E-#x0B9F] +|[#x0BA3-#x0BA4] |[#x0BA8-#x0BAA] |[#x0BAE-#x0BB5] |[#x0BB7-#x0BB9] +|[#x0C05-#x0C0C] |[#x0C0E-#x0C10] |[#x0C12-#x0C28] |[#x0C2A-#x0C33] +|[#x0C35-#x0C39] |[#x0C60-#x0C61] |[#x0C85-#x0C8C] |[#x0C8E-#x0C90] +|[#x0C92-#x0CA8] |[#x0CAA-#x0CB3] |[#x0CB5-#x0CB9] |#x0CDE +|[#x0CE0-#x0CE1] |[#x0D05-#x0D0C] |[#x0D0E-#x0D10] |[#x0D12-#x0D28] +|[#x0D2A-#x0D39] |[#x0D60-#x0D61] |[#x0E01-#x0E2E] |#x0E30 +|[#x0E32-#x0E33] |[#x0E40-#x0E45] |[#x0E81-#x0E82] |#x0E84 +|[#x0E87-#x0E88] |#x0E8A |#x0E8D |[#x0E94-#x0E97] +|[#x0E99-#x0E9F] |[#x0EA1-#x0EA3] |#x0EA5 |#x0EA7 +|[#x0EAA-#x0EAB] |[#x0EAD-#x0EAE] |#x0EB0 |[#x0EB2-#x0EB3] +|#x0EBD |[#x0EC0-#x0EC4] |[#x0F40-#x0F47] |[#x0F49-#x0F69] +|[#x10A0-#x10C5] |[#x10D0-#x10F6] |#x1100 |[#x1102-#x1103] +|[#x1105-#x1107] |#x1109 |[#x110B-#x110C] |[#x110E-#x1112] +|#x113C |#x113E |#x1140 |#x114C |#x114E |#x1150 +|[#x1154-#x1155] |#x1159 |[#x115F-#x1161] |#x1163 +|#x1165 |#x1167 |#x1169 |[#x116D-#x116E] |[#x1172-#x1173] +|#x1175 |#x119E |#x11A8 |#x11AB |[#x11AE-#x11AF] +|[#x11B7-#x11B8] |#x11BA |[#x11BC-#x11C2] |#x11EB +|#x11F0 |#x11F9 |[#x1E00-#x1E9B] |[#x1EA0-#x1EF9] +|[#x1F00-#x1F15] |[#x1F18-#x1F1D] |[#x1F20-#x1F45] |[#x1F48-#x1F4D] +|[#x1F50-#x1F57] |#x1F59 |#x1F5B |#x1F5D |[#x1F5F-#x1F7D] +|[#x1F80-#x1FB4] |[#x1FB6-#x1FBC] |#x1FBE |[#x1FC2-#x1FC4] +|[#x1FC6-#x1FCC] |[#x1FD0-#x1FD3] |[#x1FD6-#x1FDB] |[#x1FE0-#x1FEC] +|[#x1FF2-#x1FF4] |[#x1FF6-#x1FFC] |#x2126 |[#x212A-#x212B] +|#x212E |[#x2180-#x2182] |[#x3041-#x3094] |[#x30A1-#x30FA] +|[#x3105-#x312C] |[#xAC00-#xD7A3] + + +Ideographic[#x4E00-#x9FA5] |#x3007 |[#x3021-#x3029] + + +CombiningChar[#x0300-#x0345] |[#x0360-#x0361] |[#x0483-#x0486] +|[#x0591-#x05A1] |[#x05A3-#x05B9] |[#x05BB-#x05BD] |#x05BF +|[#x05C1-#x05C2] |#x05C4 |[#x064B-#x0652] |#x0670 +|[#x06D6-#x06DC] |[#x06DD-#x06DF] |[#x06E0-#x06E4] |[#x06E7-#x06E8] +|[#x06EA-#x06ED] |[#x0901-#x0903] |#x093C |[#x093E-#x094C] +|#x094D |[#x0951-#x0954] |[#x0962-#x0963] |[#x0981-#x0983] +|#x09BC |#x09BE |#x09BF |[#x09C0-#x09C4] |[#x09C7-#x09C8] +|[#x09CB-#x09CD] |#x09D7 |[#x09E2-#x09E3] |#x0A02 +|#x0A3C |#x0A3E |#x0A3F |[#x0A40-#x0A42] |[#x0A47-#x0A48] +|[#x0A4B-#x0A4D] |[#x0A70-#x0A71] |[#x0A81-#x0A83] |#x0ABC +|[#x0ABE-#x0AC5] |[#x0AC7-#x0AC9] |[#x0ACB-#x0ACD] |[#x0B01-#x0B03] +|#x0B3C |[#x0B3E-#x0B43] |[#x0B47-#x0B48] |[#x0B4B-#x0B4D] +|[#x0B56-#x0B57] |[#x0B82-#x0B83] |[#x0BBE-#x0BC2] |[#x0BC6-#x0BC8] +|[#x0BCA-#x0BCD] |#x0BD7 |[#x0C01-#x0C03] |[#x0C3E-#x0C44] +|[#x0C46-#x0C48] |[#x0C4A-#x0C4D] |[#x0C55-#x0C56] |[#x0C82-#x0C83] +|[#x0CBE-#x0CC4] |[#x0CC6-#x0CC8] |[#x0CCA-#x0CCD] |[#x0CD5-#x0CD6] +|[#x0D02-#x0D03] |[#x0D3E-#x0D43] |[#x0D46-#x0D48] |[#x0D4A-#x0D4D] +|#x0D57 |#x0E31 |[#x0E34-#x0E3A] |[#x0E47-#x0E4E] +|#x0EB1 |[#x0EB4-#x0EB9] |[#x0EBB-#x0EBC] |[#x0EC8-#x0ECD] +|[#x0F18-#x0F19] |#x0F35 |#x0F37 |#x0F39 |#x0F3E +|#x0F3F |[#x0F71-#x0F84] |[#x0F86-#x0F8B] |[#x0F90-#x0F95] +|#x0F97 |[#x0F99-#x0FAD] |[#x0FB1-#x0FB7] |#x0FB9 +|[#x20D0-#x20DC] |#x20E1 |[#x302A-#x302F] |#x3099 +|#x309A + + +Digit[#x0030-#x0039] |[#x0660-#x0669] |[#x06F0-#x06F9] +|[#x0966-#x096F] |[#x09E6-#x09EF] |[#x0A66-#x0A6F] |[#x0AE6-#x0AEF] +|[#x0B66-#x0B6F] |[#x0BE7-#x0BEF] |[#x0C66-#x0C6F] |[#x0CE6-#x0CEF] +|[#x0D66-#x0D6F] |[#x0E50-#x0E59] |[#x0ED0-#x0ED9] |[#x0F20-#x0F29] + + +Extender#x00B7 |#x02D0 |#x02D1 |#x0387 |#x0640 +|#x0E46 |#x0EC6 |#x3005 |[#x3031-#x3035] |[#x309D-#x309E] +|[#x30FC-#x30FE] + + + + diff --git a/test/src/resources/xsdcomplex/SAXTestComplexImport.xsd b/test/src/resources/xsdcomplex/SAXTestComplexImport.xsd new file mode 100644 index 0000000..3fc482a --- /dev/null +++ b/test/src/resources/xsdcomplex/SAXTestComplexImport.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/test/src/resources/xsdcomplex/SAXTestComplexMain.xsd b/test/src/resources/xsdcomplex/SAXTestComplexMain.xsd new file mode 100644 index 0000000..1db7f8e --- /dev/null +++ b/test/src/resources/xsdcomplex/SAXTestComplexMain.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/resources/xsdcomplex/input.xml b/test/src/resources/xsdcomplex/input.xml new file mode 100644 index 0000000..32c3d0f --- /dev/null +++ b/test/src/resources/xsdcomplex/input.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/test/src/resources/xsdcomplex/multi.xml b/test/src/resources/xsdcomplex/multi.xml new file mode 100644 index 0000000..1822ed0 --- /dev/null +++ b/test/src/resources/xsdcomplex/multi.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/test/src/resources/xsdcomplex/multi_main.xsd b/test/src/resources/xsdcomplex/multi_main.xsd new file mode 100644 index 0000000..7e78d6c --- /dev/null +++ b/test/src/resources/xsdcomplex/multi_main.xsd @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/resources/xsdcomplex/multi_one.xsd b/test/src/resources/xsdcomplex/multi_one.xsd new file mode 100644 index 0000000..c7433a8 --- /dev/null +++ b/test/src/resources/xsdcomplex/multi_one.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/src/resources/xsdcomplex/multi_two.xsd b/test/src/resources/xsdcomplex/multi_two.xsd new file mode 100644 index 0000000..1aa7f29 --- /dev/null +++ b/test/src/resources/xsdcomplex/multi_two.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file