All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.globalmentor.swing.text.xml.XMLDocument Maven / Gradle / Ivy

The newest version!
/*
 * Copyright © 1996-2009 GlobalMentor, Inc. 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.globalmentor.swing.text.xml;

import java.awt.Image; //TODO del when loading routines are placed elsewhere
import java.awt.Toolkit; //TODO del when loading routines are placed elsewhere
import java.lang.ref.*;
import static java.text.MessageFormat.*;
import java.util.*;
import java.net.*;
import java.io.*;
import javax.sound.sampled.*;
import javax.swing.event.*;
import javax.swing.text.*;

import org.w3c.dom.css.CSSStyleDeclaration;
import org.w3c.dom.css.CSSStyleSheet;

import com.globalmentor.io.*;
import com.globalmentor.java.*;
import com.globalmentor.log.Log;
import com.globalmentor.model.NameValuePair;
import com.globalmentor.net.ContentType;
import com.globalmentor.net.ContentTypeConstants;
import static com.globalmentor.net.URIs.*;
import com.globalmentor.rdf.*;
import com.globalmentor.sound.sampled.SampledSounds;
import com.globalmentor.swing.event.ProgressEvent;
import com.globalmentor.swing.text.*;
import com.globalmentor.swing.text.xml.css.*;
import com.globalmentor.w3c.spec.CSS;
import com.globalmentor.xml.dom.impl.stylesheets.css.AbstractXMLCSSStylesheetApplier;
import com.globalmentor.xml.dom.impl.stylesheets.css.XMLCSSStyleDeclaration;

/**
 * A document that models XML.
 * @author Garret Wilson
 */
public class XMLDocument extends BasicStyledDocument {

	/** The task of applying a stylesheet. */
	public static final String APPLY_STYLESHEET_TASK = "applyStylesheet";

	/**
	 * The character used to mark the end of an element so that caret positioning will work correctly at the end of block views.
	 */
	//TODO fix	final static char ELEMENT_END_CHAR=CharacterConstants.ZERO_WIDTH_NO_BREAK_SPACE_CHAR;	
	//TODO fix; the ZWNBSP seems to make Swing want to break a line early or something
	//TODO fix static final char ELEMENT_END_CHAR=CharacterConstants.ZERO_WIDTH_SPACE_CHAR;	

	//TODO fix static final char ELEMENT_END_CHAR='\n';	

	final static char ELEMENT_END_CHAR = Characters.ZERO_WIDTH_SPACE_CHAR;
	final static String ELEMENT_END_STRING = String.valueOf(ELEMENT_END_CHAR);
	//TODO fix static final char ELEMENT_END_CHAR=CharacterConstants.ZERO_WIDTH_NO_BREAK_SPACE_CHAR;	
	//TODO fix	final static char ELEMENT_END_CHAR=CharacterConstants.PARAGRAPH_SIGN_CHAR;	

	/** A map of soft references to resources that have been loaded. */
	private final Map> resourceReferenceMap = new HashMap>();

	/**
	 * Returns a cached resource identified by the URI, if the object's memory has not been reclaimed.
	 * @param resourceURI The URI of the requested resource.
	 * @return The resource, if it has been cached and is still referenced in the JVM, or null if the resource's memory has been reclaimed or the
	 *         object has never been cached.
	 */
	protected Object getCachedResource(final URI resourceURI) {
		final Reference resourceReference = resourceReferenceMap.get(resourceURI); //return a reference to the cached resource, if available
		if(resourceReference != null) { //if we found a reference to the resource
			final Object resource = resourceReference.get(); //get the resource itself
			if(resource != null) //if we still have the resource cached
				return resource; //return the resource
			else
				resourceReferenceMap.remove(resourceURI); //remove the reference from the cache, since it is no longer useful
		}
		return null; //show that either the object wasn't cached, or its memory has been reclaimed
	}

	/**
	 * Stores a resource as a reference in the cache. The resource will only stay in the cache until the JVM decides its needs to reclaim its memory.
	 * @param resourceURI The URI of the resource being cached.
	 * @param resource The resource to cache.
	 */
	protected void putCachedResource(final URI resourceURI, final Object resource) {
		//store the resource in the map as a soft reference
		resourceReferenceMap.put(resourceURI, new SoftReference(resource));
	}

	/** The object that applies stylesheets to the document. */
	private final SwingXMLCSSStylesheetApplier swingStylesheetApplier;

	/** @return The object that applies stylesheets to the document. */
	protected SwingXMLCSSStylesheetApplier getSwingStylesheetApplier() {
		return swingStylesheetApplier;
	}

	/**
	 * Constructor.
	 * @param uriInputStreamable The source of input streams for resources.
	 * @throws NullPointerException if the new source of input streams is null.
	 */
	public XMLDocument(final URIInputStreamable uriInputStreamable) {
		//TODO fix		super(new PureGapContent(BUFFER_SIZE_DEFAULT), new XMLCSSStyleContext());	//construct the parent class, specifying our own type of style context that knows how to deal with CSS attributes
		super(new XMLCSSStyleContext(), uriInputStreamable); //construct the parent class, specifying our own type of style context that knows how to deal with CSS attributes
		swingStylesheetApplier = new SwingXMLCSSStylesheetApplier(); //create a new Swing stylesheet applier
	}

	/**
	 * Constructs an html document with the default content storage implementation and the given style/attribute storage mechanism.
	 *
	 * @param styles the styles
	 */
	/*TODO fix
			public HTMLDocument(StyleSheet styles) {
		this(new GapContent(BUFFER_SIZE_DEFAULT), styles);
			}
	*/

	/**
	 * Creates the root element to be used to represent the default document structure. TODO make this somehow know what type of document to make -- what
	 * vocabulary. For now, we'll default to HTML.
	 * @return The element base.
	 */
	protected AbstractElement createDefaultRoot() {
		return super.createDefaultRoot(); //TODO testing
		/*TODO fix
				final XMLCSSStyleDeclaration blockCSSStyle=new XMLCSSStyleDeclaration(); //create a new style declaration
				blockCSSStyle.setDisplay(XMLCSSConstants.CSS_DISPLAY_BLOCK);	//make the style declaration display: block
				final MutableAttributeSet htmlAttributeSet=createAttributeSet("html", null, blockCSSStyle);  //TODO testing; comment; use a constant; fix namespace
				final MutableAttributeSet bodyAttributeSet=createAttributeSet("body", null, blockCSSStyle);  //TODO testing; comment; use a constant; fix namespace
				final MutableAttributeSet pAttributeSet=createAttributeSet("p", null, blockCSSStyle);  //TODO testing; comment; use a constant; fix namespace
		//TODO del		XMLCSSStyleConstants.setParagraphView(pAttributeSet, true);	//show that the paragraph element should have a paragraph view


				writeLock();  //grab a write-lock for this initialization and abandon it during initialization so in normal operation we can detect an illegitimate attempt to mutate attributes
				final Element[] buff=new Element[1];  //create an element array for insertion of elements

				final BranchElement section=new SectionElement(); //create a new section
				final BranchElement html=new BranchElement(section, htmlAttributeSet); //create a new paragraph to represent the document
				final BranchElement body=new BranchElement(html, bodyAttributeSet); //create a new paragraph to represent the HTML body
				final BranchElement p=new BranchElement(body, pAttributeSet); //create a new paragraph to represent the paragraph
				final LeafElement leaf=new LeafElement(p, null, 0, 1);  //create the leaf element
				buff[0]=leaf; //insert the leaf
				p.replace(0, 0, buff);
				buff[0]=p;  //insert the p
				body.replace(0, 0, buff);
				buff[0]=body;  //insert the body
				html.replace(0, 0, buff);
		*/

		/*TODO del

					BranchElement paragraph = new BranchElement(section, null);

					LeafElement brk = new LeafElement(paragraph, null, 0, 1);
					buff[0] = brk;
					paragraph.replace(0, 0, buff);

					final Element[] sectionBuffer=new Element[2];  //TODO testing
					sectionBuffer[0] = html;
					sectionBuffer[1] = paragraph;
					section.replace(0, 0, sectionBuffer);
		*/
		/*TODO fix
				buff[0]=html;  //insert the html
				section.replace(0, 0, buff);
				writeUnlock();
				return section;
		*/
	}

	/**
	 * Creates an attribute set for the given XML node.
	 * @param node The XML node, such as an element or text.
	 * @param baseURI The base URI of the document, used for generating full target URIs for quick searching, or null if there is no base URI or if
	 *          the base URI is not applicable.
	 * @return An attribute set reflecting the CSS attributes of the element.
	 */
	/*TODO decide if we want this
		protected MutableAttributeSet createAttributeSet(final Node xmlNode, final URI baseURI)
		{
			final String namespaceURI=xmlNode.getNamespaceURI();  //get the node namespace URI
			final MutableAttributeSet attributeSet=createAttributeSet(namespaceURI!=null ? URI.create(namespaceURI) : null, xmlNode.getNodeName());	//create a new attribute for this node
			//TODO give every attribute set a default empty CSS style; later fix this in the application section to create as needed and to clear them before application
	//TODO del when moved to the set-style routines		XMLCSSStyleConstants.setXMLCSSStyle(attributeSet, new XMLCSSStyleDeclaration());	//give every attribute set a default empty CSS style
			switch(xmlNode.getNodeType()) {	//see what type of node for which to create an attribute set
				case Node.ELEMENT_NODE: //if this node is an element
					{
						final org.w3c.dom.Element xmlElement=(org.w3c.dom.Element)xmlNode;  //cast the node to an element
						final CSSStyleDeclaration cssStyle=getXMLStylesheetApplier().getStyle(xmlElement);	//see if we've already applied a style to this element
						if(cssStyle!=null)
						{
							XMLCSSStyleUtilities.setXMLCSSStyle(attributeSet, cssStyle);	
						}
						else
						{
							//give every attribute set a default empty CSS style; if not, this will cause huge performance hits when trying to create them on the fly when styles are applied TODO recheck
							XMLCSSStyleUtilities.setXMLCSSStyle(attributeSet, new XMLCSSStyleDeclaration());
						}
						final NamedNodeMap attributeNodeMap=xmlElement.getAttributes(); //get a reference to the attributes
						//store the XML attributes
						for(int attributeIndex=0; attributeIndex
	 * 
  • image/* - java.awt.Image The image may not be loaded.
  • *
  • audio/* - javax.sound.sampled.Line Usually this will be of type javax.sound.sampled.Clip and will have been opened.
  • * * @param href The specified location of the resource. * @return The specified resource. * @throws URISyntaxException Thrown if the given location results in a syntactically incorrect URI. * @throws IOException Thrown if the specified resource cannot be retrieved. */ public Object getResource(final String href) throws URISyntaxException, IOException { final ContentType mediaType = getResourceMediaType(href); //get the media type of the resource if(mediaType != null) { //if we think we know the media type of the file involved final URI resourceURI = getResourceURI(href); //create a URI based upon the base URI and the given file location return getResource(resourceURI, mediaType); //get the resource from its URI and its media type } else throw new IOException(href + " has an unrecognized media type."); //TODO i18n } /** * Gets a particular resource from the given location. If the resource is cached, the cached copy will be returned. If the document is loaded, it will be * stored in the local weak cache. The return types for particular media types are as follows: *
      *
    • image/* - java.awt.Image The image may not be loaded.
    • *
    • audio/* - javax.sound.sampled.Line Usually this will be of type javax.sound.sampled.Clip and will have been opened.
    • *
    * @param uri The URI location of the resource. * @param mediaType The media type of the resource. * @return The specified resource. * @throws IOException Thrown if the specified resource cannot be retrieved. */ protected Object getResource(final URI uri, final ContentType mediaType) throws IOException { Object resource = getCachedResource(uri); //see if the resource is cached if(resource != null) { //if the resource was cached if(resource instanceof Clip) { //if this resource is a clip TODO hack; fix to have a special getClip() method final Clip clip = (Clip)resource; //cast the resource to a clip if(clip.isRunning()) //if the clip is already running clip.stop(); //stop playing the clip clip.setFramePosition(0); //start at the beginning of the clip } return resource; //return the resource } else //if the resource wasn't cached return loadResource(uri, mediaType); //load and return the resource } /** * Gets the URI of a particular resource. If the given href is relative, it is correctly normalized to an absolute URI. This version assumes * relative locations are relative to the base URI unless the base URI is null, in which case the href is assumed to be absolute. * @param href The specified location of the resource. * @return The URI of the specified resource. * @throws IllegalArgumentException if the given string violates RFC 2396. * @see #getBaseURI */ public URI getResourceURI(final String href) { return resolve(getBaseURI(), href); //create and return a URI based upon the base URI, if any, and the given file location } /** * Gets the media type of a particular resource. * @param href The specified location of the resource. * @return The media type of the specified resource, or null if the media type cannot be determined. */ public ContentType getResourceMediaType(final String href) { ContentType mediaType = null; //we start out not knowing the media type of the resource final RDFResource publication = getPublication(); //get the publication description if(publication != null) { //if there is a description of the publication /*TODO fix with URF //get the manifest resource which represents the requested resource final RDFResource resource=XPackageUtilities.getManifestItemByLocationHRef(publication, getBaseURI(), href); if(resource!=null) { //if the item is listed in the manifest mediaType=Marmot.getMediaType(resource); //get the resource's media type } */ } if(mediaType == null) { //if we couldn't find a media type from the publication description mediaType = Files.getMediaType(href); //get the media type from the extension of the href, if any } return mediaType; //return the media type we found, if any } /** * Opens an input stream to the given location, based upon the document's base URI. The input stream should be closed when it is no longer needed. * @param href The specified location of the resource. * @return An open input stream to the resource. * @throws URISyntaxException Thrown if the given location results in a syntactically incorrect URI. * @throws IOException Thrown if an input stream to the specified resource cannot be created. * @see #getBaseURI //TODO check about returning null if the resource is not found */ public InputStream getResourceAsInputStream(final String href) throws URISyntaxException, IOException { final URI resourceURI = getResourceURI(href); //create a URI based upon the base URI and the given file location return getResourceAsInputStream(resourceURI); //get an input stream from this URI } /** * Opens an input stream to the given URI. The input stream should be closed when it is no longer needed. This implementation delegates to * getInputStream(). * @param uri The specified location of the resource. * @return An open input stream to the resource. * @throws IOException Thrown if an input stream to the specified resource cannot be created. //TODO check about returning null if the resource is not found * @see #getInputStream(URI) */ public InputStream getResourceAsInputStream(final URI uri) throws IOException { //TODO del when we can in favor of getInputStream() return getInputStream(uri); //delegate to our URIInputStreamable method } /** * Loads a particular resource from the given location. The loaded resource will be stored in the local weak cache. The return types for particular media * types are as follows: *
      *
    • image/* - java.awt.Image The image may not be loaded.
    • *
    • audio/* - javax.sound.sampled.Line Usually this will be of type javax.sound.sampled.Clip and will have been opened.
    • *
    * @param resourceURI The specified location of the resource. * @param mediaType The media type of the resource. * @return The specified resource. * @throws IOException Thrown if the specified resource cannot be retrieved. */ protected Object loadResource(final URI resourceURI, final ContentType mediaType) throws IOException { //TODO change this to loadImage, loadClip, etc. Object resource; //this will be assigned if we run into no errors if(mediaType.getPrimaryType().equals(ContentType.IMAGE_PRIMARY_TYPE)) { //if this is an image final String mediaSubType = mediaType.getSubType(); //get the media sub-type //if this is a GIF, JPEG, PNG TODO fix, or X_BITMAP image if(mediaSubType.equals(ContentTypeConstants.GIF_SUBTYPE) || mediaSubType.equals(ContentTypeConstants.JPEG_SUBTYPE) || mediaSubType.equals(ContentTypeConstants.PNG_SUBTYPE)/*TODO fix || mediaSubType.equals(MediaTypeConstants.X_BITMAP)*/) { //TODO since we're opening directly from a file, maybe there is a better way to do this /*TODO this works; fix to use our own caching final ImageIcon imageIcon=new javax.swing.ImageIcon(resourceURL); //create an ImageIcon from the file resource=imageIcon.getImage(); //TODO change to return an image later */ /*TODO del when works final Toolkit toolkit=Toolkit.getDefaultToolkit(); //get the default toolkit final Image image=toolkit.createImage(resourceURL); //TODO testing; does this return null if it doesn't exist? */ final InputStream resourceInputStream = getResourceAsInputStream(resourceURI); //get an input stream to the resource try { final byte[] imageBytes = InputStreams.getBytes(resourceInputStream); //read the bytes from the input stream final Toolkit toolkit = Toolkit.getDefaultToolkit(); //get the default toolkit final Image image = toolkit.createImage(imageBytes); //create an image from the bytes resource = image; //TODO testing } finally { resourceInputStream.close(); //always close the input stream after we're finished with it } //TODO del when works ImageUtilities.loadImage(image); //load the image } else //if we don't recognize this image type throw new IOException("Unrecognized image type: \"" + mediaType.getSubType() + "\"; only \"" + ContentTypeConstants.JPEG_SUBTYPE + "\", \"" + ContentTypeConstants.PNG_SUBTYPE + "\", and \"" + ContentTypeConstants.GIF_SUBTYPE + "\" are currently supported."); //TODO i18n TODO fix for other image types } else if(Audio.isAudio(mediaType)) { //if this is an audio media type final InputStream inputStream = new BufferedInputStream(getResourceAsInputStream(resourceURI)); //get a buffered input stream to the audio //TODO we should really close the input stream if something goes wrong try { final Clip clip = (Clip)SampledSounds.getDataLine(inputStream, Clip.class); //get a clip from the input stream resource = clip; //return the clip //TODO del return clip; //return the clip without caching it, because caching a clip doesn't allow it to be played again } catch(UnsupportedAudioFileException unsupportedAudioFileException) { throw (IOException)new IOException("The format of " + resourceURI + " of type " + mediaType + " is unsupported.") .initCause(unsupportedAudioFileException); //TODO i18n } catch(LineUnavailableException lineUnavailableException) { throw (IOException)new IOException("There is no line available to the audio file " + resourceURI + " of type " + mediaType + ".") .initCause(lineUnavailableException); //TODO i18n } } else //if we don't recognize this media type throw new IOException("Unrecognized media type: " + mediaType); //TODO i18n putCachedResource(resourceURI, resource); //cache the resource in case we need to use it again return resource; //return the resource we found } /** * Inserts a group of new elements into the document * @param offset the starting offset * @data the element data * @throws BadLocationException for an invalid starting offset * @see StyledDocument#insert * @throws BadLocationException if the given position does not represent a valid location in the associated document. */ //TODO why do we override this? protected void insert(int offset, ElementSpec[] data) throws BadLocationException { super.insert(offset, data); } /** * Updates document structure as a result of text insertion. This will happen within a write lock. This implementation simply parses the inserted content for * line breaks and builds up a set of instructions for the element buffer. * * @param chng a description of the document change * @param attr the attributes */ protected void insertUpdate(DefaultDocumentEvent chng, AttributeSet attr) { Log.trace("inside XMLDocument insertupdate"); /*TODO del; testing bidiarray Log.trace("XMLDocument.insertUpdate()"); final int chngStart = chng.getOffset(); final int chngEnd = chngStart + chng.getLength(); Log.trace("change start: "+chngStart+" change end: "+chngEnd); final int firstPStart = getParagraphElement(chngStart).getStartOffset(); final int lastPEnd = getParagraphElement(chngEnd).getEndOffset(); Log.trace("first paragrah start: "+firstPStart+" last paragraph end: "+lastPEnd); */ /*TODO fix if(attr == null) { attr = contentAttributeSet; } // If this is the composed text element, merge the content attribute to it else if (attr.isDefined(StyleConstants.ComposedTextAttribute)) { ((MutableAttributeSet)attr).addAttributes(contentAttributeSet); } */ super.insertUpdate(chng, attr); //TODO del applyxStyles(); //TODO testing; put in the correct place, and make sure this gets called when repaginating, if we need to } /** * Initialize the document to reflect the given element structure (i.e. the structure reported by the getDefaultRootElement method. If the * document contained any data it will first be removed. *

    * This version is given public access so that it can be accessed by the editor kit. *

    * @param elementSpecs The array of element specifications that define the document. * @see XMLEditorKit#setXML(org.w3c.dom.Document[], URI[], MediaType[], XMLDocument) */ public void create(ElementSpec[] elementSpecs) { super.create(elementSpecs); //create the document normally try { //remove the ending dumming '\n' added by Swing if(getLength() > 0) { //if we have any characters //TODO del; this doesn't even work, as getLength() and remove() ignore the ending '\n'---this only removed the '\n' already present in the content final String text = getText(getLength() - 1, 1); //TODO comment if("\n".equals(text)) { //if the document ends with an end-of-line character remove(getLength() - 1, 1); //remove the last end-of-line character } } /*TODO del when works if(getLength()>1) { //if we have more than one character final String text=getText(getLength()-2, 2); if("\n\n".equals(text)) { //if the document ends with two end-of-line characters remove(getLength()-1, 1); //remove the last end-of-line character } } */ } catch(BadLocationException badLocationException) { throw (AssertionError)new AssertionError(badLocationException.getMessage()).initCause(badLocationException); } // TODO fix applyStyles(); //TODO testing; put in the correct place, and make sure this gets called when repaginating, if we need to /*TODO fix writeLock(); //lock the document for writing TODO do we really need to do this, as applying styles doesn't modify the document? final Element rootSwingElement=getRootElements()[0]; //get the first root element of the document -- this contains an element tree for each document loaded final int swingDocumentElementCount=rootSwingElement.getElementCount(); //find out how many root elements there are for(int swingDocumentElementIndex=0; swingDocumentElementIndex0) { //if this is not the first document to insert stringBuilder.append(CharacterConstants.OBJECT_REPLACEMENT_CHAR); //append a character for the page break element to represent } getContent(xmlDocument, stringBuilder); } contentEdit=content.insertString(0, stringBuilder.toString()); } final SectionElement sectionElement=new SectionElement(); //create a section element for all the data final Element[] childElements=new Element[xmlDocumentArray.length*2-1]; //create a new array of child elements, allowing for interspersed page breaks int offset=0; for(int xmlDocumentIndex=0; xmlDocumentIndex0) { //if this is not the first document to insert //TODO check to see if we should actually do this, first (from the CSS attributes) // TODO del System.out.println("Adding page break element."); //TODO del childElements[xmlDocumentIndex*2-1]=createPageBreakElement(sectionElement, offset); //put a page break before this document offset=childElements[xmlDocumentIndex*2-1].getEndOffset(); } childElements[xmlDocumentIndex*2]=createElement(sectionElement, offset, xmlDocument, baseURI); //add the child element for this document offset=childElements[xmlDocumentIndex*2].getEndOffset(); final MutableAttributeSet documentAttributeSet=(MutableAttributeSet)childElements[xmlDocumentIndex].getAttributes(); if(baseURI!=null) { //if there is a base URI XMLStyleUtilities.setBaseURI(documentAttributeSet, baseURI); //add the base URI as an attribute XMLStyleUtilities.setTargetURI(documentAttributeSet, baseURI); //because this element is the root of the document, its base URI acts as a linking target as well; store the target URI for quick searching } if(mediaType!=null) { //if there is a media type XMLStyleUtilities.setMediaType(documentAttributeSet, mediaType); //add the media type as an attribute } final DocumentType documentType=xmlDocument.getDoctype(); //get the XML document's doctype, if any if(documentType!=null) { //if this document has a doctype if(documentType.getPublicId()!=null) //if the document has a public ID XMLStyleUtilities.setXMLDocTypePublicID(documentAttributeSet, documentType.getPublicId()); //store the public ID if(documentType.getSystemId()!=null) //if the document has a public ID XMLStyleUtilities.setXMLDocTypeSystemID(documentAttributeSet, documentType.getSystemId()); //store the system ID } //store the processing instructions final List processingInstructionList=XMLUtilities.getNodesByName(xmlDocument, Node.PROCESSING_INSTRUCTION_NODE, "*", false); //get a list of all the processing instructions in the document TODO use a constant here if(processingInstructionList.size()>0) { //if there are processing instructions final NameValuePair[] processingInstructions=new NameValuePair[processingInstructionList.size()]; //create enough name/value pairs for processing instructions for(int processingInstructionIndex=0; processingInstructionIndex childElementList=new ArrayList(childNodeCount>0 ? childNodeCount : 1); //create a list of child elements we'll create; we'll never have more than there are XML child nodes (unless there are no child nodes) if(childNodeCount>0) { //if this element has children for(int childIndex=0; childIndexgetDefaultRootElement method. If the * document contained any data it will first be removed. */ /*TODO fix protected void create(ElementSpec[] data) { try { if (getLength() != 0) { remove(0, getLength()); } writeLock(); // install the content Content c = getContent(); int n = data.length; StringBuffer sb = new StringBuffer(); for (int i = 0; i < n; i++) { ElementSpec es = data[i]; if (es.getLength() > 0) { sb.append(es.getArray(), es.getOffset(), es.getLength()); } } UndoableEdit cEdit = c.insertString(0, sb.toString()); // build the event and element structure int length = sb.length(); DefaultDocumentEvent evnt = new DefaultDocumentEvent(0, length, DocumentEvent.EventType.INSERT); evnt.addEdit(cEdit); buffer.create(length, data, evnt); // update bidi (possibly) super.insertUpdate(evnt, null); // notify the listeners evnt.end(); fireInsertUpdate(evnt); fireUndoableEditUpdate(new UndoableEditEvent(this, evnt)); } catch (BadLocationException ble) { throw new StateInvariantError("problem initializing"); } finally { writeUnlock(); } } */ /** * Finds the element with the matching attribute. * @param attribute The attribute to compare. * @param value The value to match. * @return The element with the matching attribute, or null if none could be found. */ //TODO maybe make this protected and add a function that only looks for the target ID public Element getElement(Object attribute, Object value) { return getElement(getDefaultRootElement(), attribute, value); //start searching from the root element } /** * Returns the child element of the specified element that contains the desired attribute with the given value, or null if no element has an * attribute with the desired value. This function is not thread-safe. //TODO del if not needed If searchLeafAttributes is true, and the element * is a leaf, //TODO del if not needed * a leaf, any attributes that are instances of HTML.Tag with a //TODO del if not needed * value that is an AttributeSet * will also be checked. * @param element The element on which to start the search * @param attribute The attribute to compare. * @param value The value to match. * @return The element with the matching attribute, or null if none could be found. */ protected Element getElement(Element element, Object attribute, Object value/*TODO del if not needed, boolean searchLeafAttributes*/) { Log.trace("XMLDocument.getElement() comparing value: ", value); final AttributeSet attributeSet = element.getAttributes(); //get the attributes of this element if(attributeSet != null && attributeSet.isDefined(attribute)) { //if there are attributes and this attribute is defined Log.trace("comparing to: ", attributeSet.getAttribute(attribute)); //TODO del if(value.equals(attributeSet.getAttribute(attribute))) //if the value matches return element; //return this element /*TODO del when works; recheck exactly what this kludge was doing else //if the value doesn't match, we'll see if they are trying to match the target ID TODO this is a big kludge to get linking to work with OEB in the short term //TODO this kludge checks to see if we're looking for a target ID; if so, // and we're looking for a file (not a fragment), see if the part before // the '#' matches (the first element, for now, should have at least the // full path for the target ID { Log.trace("element doesn't match: ", attributeSet.getAttribute(attribute)); if(attribute.equals(XMLStyleConstants.TARGET_ID_PATH_ATTRIBUTE_NAME)) { //if they are looking for the target ID final String compareValue=(String)value; //cast to a string the attribute that we're comparing Log.trace("comparing with: ", compareValue); if(compareValue.indexOf('#')==-1) { //if we're looking for an absolute target ID (not a fragment) String thisValue=(String)attributeSet.getAttribute(attribute); //get the attribute we're comparing with final int poundIndex=thisValue.indexOf('#'); //get the index of any pound symbol in this attribute if(poundIndex!=-1) //if this attribute has a '#' thisValue=thisValue.substring(0, poundIndex); //remove the pound sign and everything after it if(compareValue.equals(thisValue)) //if the value matches return element; //return this element } } } */ } //TODO del if not needed if(!element.isLeaf()) //if the //TODO del if not needed { for(int elementIndex = 0, maxElementIndex = element.getElementCount(); elementIndex < maxElementIndex; ++elementIndex) { //look at each child element //see if the child element can find the attribute final Element childReturnValue = getElement(element.getElement(elementIndex), attribute, value/*TODO del, searchLeafAttributes*/); if(childReturnValue != null) //if the child find a matching attribute return childReturnValue; //return what the child's found } return null; //if we couldn't find matches, return null } /*TODO del if not needed else if (searchLeafAttributes && attr != null) { // For some leaf elements we store the actual attributes inside // the AttributeSet of the Element (such as anchors). Enumeration names = attr.getAttributeNames(); if (names != null) { while (names.hasMoreElements()) { Object name = names.nextElement(); if ((name instanceof HTML.Tag) && (attr.getAttribute(name) instanceof AttributeSet)) { AttributeSet check = (AttributeSet)attr. getAttribute(name); if (check.isDefined(attribute) && value.equals(check.getAttribute(attribute))) { return e; } } } } } return null; } */ /** * Gets the paragraph element at the offset pos. *

    * The paragraph elements of XMLDocument can have multiple sub-layers of elements, representing nested XML elements such as * <strong>; these will be translated into a single layer of views for each string of content. *

    *

    * The DefaultStyledDocument version of this element, on the other hand, assumes that each paragraph will only have one single layer of content * elements, so it simply finds the correct content element and returns its parent. *

    *

    * This version finds the first element up the chain that is not an inline element. If all elements up the chain are inline, this method functions identical * to that of DefaultStyledDocument. *

    *

    * This version of the method is crucial; without it, AbstractDocument.calculateBidiLevels() can receive incorrect paragraph beginning and ending * information and throw an ArrayIndexOutOfBoundsException. Editing also requires the functionality in this method. *

    * @param pos The starting offset (>=0); * @return The element with the paragraph view attribute set, or if none is set, the parent element of the leaf element at the given position. */ public Element getParagraphElement(int pos) { Log.trace("pos: ", pos); //TODO del final Element defaultParagraphElement = super.getParagraphElement(pos); //get the default paragraph element final Element rootElement = getDefaultRootElement(); //get the default root element so we'll know when to stop looking up the chain Element paragraphElement = defaultParagraphElement; //we'll check the default paragraph element -- perhaps it really is a paragraph element while(paragraphElement != null && paragraphElement != rootElement) { //stop looking when we've reached the root element or run out of elements final AttributeSet paragraphAttributeSet = paragraphElement.getAttributes(); //get the paragraph's attributes assert paragraphAttributeSet != null : "Paragraph has no attributes."; Log.trace("this paragraph attribute set: ", com.globalmentor.swing.text.AttributeSets.getAttributeSetString(paragraphAttributeSet)); //TODO del; use relative class name final CSSStyleDeclaration paragraphCSSStyle = XMLCSSStyles.getXMLCSSStyle(paragraphAttributeSet); //get the CSS style of the element (this method makes sure the attributes are present) if(!CSS.isDisplayInline(paragraphCSSStyle)) //if this element is marked as a paragraph //TODO del whenw orks if(XMLStyleConstants.isParagraphView(paragraphAttributeSet)) //if this element is not marked as a paragraph { Log.trace("paragraph is paragraph"); //TODO del return paragraphElement; //return the paragraph element } paragraphElement = paragraphElement.getParentElement(); //since this element wasn't a paragraph element, try the one above it } return defaultParagraphElement; //we couldn't find anything marked as a paragraph, so return the default } /** * Returns true if the text in the range p0 to p1 is left to right. Imported from {@link AbstractDocument} version 1.112 02/02/00 by * Timothy Prinzing because that version has class access and cannot be called from the revised com.globalmentor.swing.text.GlyphPainter, which in turn has * been taken out of its package so that it can be created by com.globalmentor.swing.text.TextLayoutStrategy, which had to be rewritten to allow antialised * text because of a JDK 1.3.x bug that caused a Graphic/code> object not to correctly create a FontRenderContext that recognized the antialised font property. */ public boolean isLeftToRight(int p0, int p1) { if(!getProperty(Java.I18N_PROPERTY_NAME).equals(Boolean.TRUE)) { return true; } Element bidiRoot = getBidiRootElement(); int index = bidiRoot.getElementIndex(p0); Element bidiElem = bidiRoot.getElement(index); //TODO is this causing problems with our innovations for inline elements? if(bidiElem.getEndOffset() >= p1) { AttributeSet bidiAttrs = bidiElem.getAttributes(); return ((StyleConstants.getBidiLevel(bidiAttrs) % 2) == 0); } return true; } /** * Discovers any referenced styles to this document, loads the stylesheets, and applies the styles to the Swing element attributes. * @param swingDocument The Swing document containing the data. */ public void applyStyles() { Log.trace("Ready to applystyles"); //TODO fix writeLock(); //get a lock on the document try { Log.trace("looking at first root element"); //TODO fix final Element rootSwingElement = getRootElements()[0]; //get the first root element of the document -- this contains an element tree for each document loaded final int swingDocumentElementCount = rootSwingElement.getElementCount(); //find out how many root elements there are for(int swingDocumentElementIndex = 0; swingDocumentElementIndex < swingDocumentElementCount; ++swingDocumentElementIndex) { //look at each root element, each of which represents an XML document final Element swingDocumentElement = rootSwingElement.getElement(swingDocumentElementIndex); //get the child element, which is the root of the document tree final AttributeSet documentAttributeSet = swingDocumentElement.getAttributes(); //get the attribute set of the document element final URI documentBaseURI = XMLStyles.getBaseURI(documentAttributeSet); //get the URI of this document final ContentType documentMediaType = XMLStyles.getMediaType(documentAttributeSet); //see what media type this document is //TODO convert external stylesheet gathering to something general final RDFResource description = XMLStyles.getDocumentDescription(documentAttributeSet); //see if there is an RDF resource describing this document final SwingXMLCSSStylesheetApplier stylesheetApplier = getSwingStylesheetApplier(); //get the stylesheet applier //TODO make sure the stylesheet applier correctly distinguishes between document base URI for internal stylesheets and publication base URI for package-level base URIs //get all stylesheets for this document final CSSStyleSheet[] styleSheets = stylesheetApplier.getStylesheets(swingDocumentElement, documentBaseURI, documentMediaType/*TODO convert to something general, description*/); //apply the stylesheets for(int i = 0; i < styleSheets.length; ++i) { //look at each stylesheet //prepare a progress message: "Applying stylesheet X to XXXXX.html" final String progressMessage = format("Applying stylesheet {0} to {1}", Integer.valueOf(i + 1), documentBaseURI != null ? documentBaseURI.toString() : "unknown"); //TODO i18n; fix documentURI if null Log.trace(progressMessage); //TODO del fireMadeProgress(new ProgressEvent(this, APPLY_STYLESHEET_TASK, progressMessage, swingDocumentElementIndex, swingDocumentElementCount)); //fire a progress message saying that we're applying a stylesheet //TODO del System.out.println("applying stylesheet: "+i+" of "+styleSheetList.getLength()); //TODO del final CSSStyleSheet cssStyleSheet = styleSheets[i]; //get a reference to this stylesheet, assuming that it's a CSS stylesheet (that's all that's currently supported) stylesheetApplier.applyStyleSheet(cssStyleSheet, swingDocumentElement); //apply the stylesheet to the document } Log.trace("applying local styles"); //TODO del fireMadeProgress(new ProgressEvent(this, APPLY_STYLESHEET_TASK, "Applying local styles", swingDocumentElementIndex, swingDocumentElementCount)); //fire a progress message saying that we're applying local styles TODO i18n stylesheetApplier.applyLocalStyles(swingDocumentElement); //apply local styles to the document TODO why don't we create one routine to do all of this? } } finally { writeUnlock(); //always release the lock on the document } } /*TODO fix public void emphasis() { //TODO testing writeLock(); //TODO testing //TODO fix final Element[] buff=new Element[1]; //create an element array for insertion of elements final Element characterElement=getCharacterElement(60); //TODO fix final AttributeSet emAttributeSet=createAttributeSet("em", XHTMLConstants.XHTML_NAMESPACE_URI.toString()); //TODO testirng final AttributeSet emAttributeSet=createAttributeSet(XHTMLConstants.XHTML_NAMESPACE_URI, "em"); //TODO testirng //TODO fix final Element branchElement=createBranchElement(characterElement.getParentElement(), emAttributeSet); //TODO fix buff[0]=branchElement; final List elementSpecList=new ArrayList(); //create an array to hold our element specs elementSpecList.add(new DefaultStyledDocument.ElementSpec(emAttributeSet, DefaultStyledDocument.ElementSpec.StartTagType)); appendElementSpecListContent(elementSpecList, "test", null, null); //TODO fix elementSpecList.add(new DefaultStyledDocument.ElementSpec(emAttributeSet, DefaultStyledDocument.ElementSpec.EndTagType)); final DefaultStyledDocument.ElementSpec[] elementSpecs=(DefaultStyledDocument.ElementSpec[])elementSpecList.toArray(new DefaultStyledDocument.ElementSpec[elementSpecList.size()]); DefaultDocumentEvent evnt = new DefaultDocumentEvent(60, 4, DocumentEvent.EventType.INSERT); //TODO fix evnt.addEdit(cEdit); //TODO fix buffer.create(1, buff, evnt); */ /*TODO fix try { insert(60, elementSpecs); } catch (BadLocationException e) { Log.error(e); } */ /*TODO fix buffer.insert(60, 4, elementSpecs, evnt); // update bidi (possibly) insertUpdate(evnt, null); // notify the listeners evnt.end(); fireInsertUpdate(evnt); fireUndoableEditUpdate(new UndoableEditEvent(this, evnt)); */ /*TODO fix // update bidi (possibly) super.insertUpdate(evnt, null); // notify the listeners evnt.end(); fireInsertUpdate(evnt); fireUndoableEditUpdate(new UndoableEditEvent(this, evnt)); */ /*TODO del final Element[] buff=new Element[1]; //create an element array for insertion of elements createBranchElement() final BranchElement section=new SectionElement(); //create a new section final BranchElement html=new BranchElement(section, htmlAttributeSet); //create a new paragraph to represent the document final BranchElement body=new BranchElement(html, bodyAttributeSet); //create a new paragraph to represent the HTML body final BranchElement p=new BranchElement(body, pAttributeSet); //create a new paragraph to represent the paragraph final LeafElement leaf=new LeafElement(p, null, 0, 1); //create the leaf element buff[0]=leaf; //insert the leaf p.replace(0, 0, buff); buff[0]=p; //insert the p body.replace(0, 0, buff); buff[0]=body; //insert the body html.replace(0, 0, buff); BranchElement paragraph = new BranchElement(section, null); LeafElement brk = new LeafElement(paragraph, null, 0, 1); buff[0] = brk; paragraph.replace(0, 0, buff); final Element[] sectionBuffer=new Element[2]; //TODO testing sectionBuffer[0] = html; sectionBuffer[1] = paragraph; section.replace(0, 0, sectionBuffer); */ /*TODO fix buff[0]=html; //insert the html section.replace(0, 0, buff); writeUnlock(); return section; */ /*TODO fix writeUnlock(); } */ /** * Inserts an XML element into the document around the indicated selection. * @param offset The offset in the document (>=0). * @param length The length (>=0). * @param elementNamespaceURI The namespace of the XML element. * @param elementQName The qualified name of the XML element. */ /*TODO fix public void insertXMLElement(final int offset, final int length, final URI elementNamespaceURI, final String elementQName) { writeLock(); //lock the document for writing final Element characterElement=getCharacterElement(offset); //get the element at the offset final AttributeSet elementAttributeSet=createAttributeSet(elementNamespaceURI, elementQName); //create an attribute set for the element final List elementSpecList=new ArrayList(); //create an array to hold our element specs elementSpecList.add(new DefaultStyledDocument.ElementSpec(elementAttributeSet, DefaultStyledDocument.ElementSpec.StartTagType)); //TODO use another Unicode character that has replacement semantics, just to make this neater and more readable appendElementSpecListContent(elementSpecList, StringUtilities.makeString('*', length), null, null); //TODO fix; comment elementSpecList.add(new DefaultStyledDocument.ElementSpec(elementAttributeSet, DefaultStyledDocument.ElementSpec.EndTagType)); final DefaultStyledDocument.ElementSpec[] elementSpecs=(DefaultStyledDocument.ElementSpec[])elementSpecList.toArray(new DefaultStyledDocument.ElementSpec[elementSpecList.size()]); DefaultDocumentEvent evnt=new DefaultDocumentEvent(offset, length, DocumentEvent.EventType.INSERT); buffer.insert(offset, length, elementSpecs, evnt); //insert the element's specifications //TODO fix insertUpdate(evnt, null); //update after the insert evnt.end(); //end the editing fireInsertUpdate(evnt); //notify listeners of the insert applyStyles(); //TODO testing fireUndoableEditUpdate(new UndoableEditEvent(this, evnt)); //notify listeners of the undoable edit writeUnlock(); //unlock the document } */ /** * Class to apply styles to Swing elements. * @author Garret Wilson */ protected class SwingXMLCSSStylesheetApplier extends AbstractXMLCSSStylesheetApplier { /** * Returns an input stream for the given URI. *

    * The calling class has the responsibility for closing the input stream. *

    * @param uri A URI to a resource. * @return An input stream to the contents of the resource represented by the given URI. * @throws IOException Thrown if an I/O error occurred. */ public InputStream getInputStream(final URI uri) throws IOException { return getResourceAsInputStream(uri); //ask the Swing document for the URI TODO maybe later change getResourceAsInputStream() to getInputStream() so that the XMLDocument is URIInputStreamable, if it isn't already } /** * Returns the object that represents the root element of the given document. * @param The object representing the XML document. * @return The object representing the root element of the XML document. */ protected Element getDocumentElement(final Element document) { return document; //in Swing the XML document is represented by the root element in the document hierarchy---in this implementation, the document element hierarchy is a direct descendant of the section element } /** * Retrieves processing instructions from the given document. * @param document The document that might contain XML processing instructions. * @return A non-null array of name-value pairs representing processing instructions. */ protected NameValuePair[] getDocumentProcessingInstructions(final Element document) { return XMLStyles.getXMLProcessingInstructions(document.getAttributes()); //get the processing instructions from the attributes of the document, which is really a Swing element } /** * Retrieves the namespace URI of the given element. * @param element The element for which the namespace URI should be returned. * @return The namespace URI of the given element. */ protected String getElementNamespaceURI(final Element element) { return XMLStyles.getXMLElementNamespaceURI(element.getAttributes()); //return the element's namespace URI from the Swing element's attributes } /** * Retrieves the local name of the given element. * @param element The element for which the local name should be returned. * @return The local name of the given element. */ protected String getElementLocalName(final Element element) { return XMLStyles.getXMLElementLocalName(element.getAttributes()); //return the element's local name from the Swing element's attributes } /** * Retrieves the value of one of the element's attributes. * @param element The element owner of the attributes. * @param attributeNamespaceURI The namespace of the attribute to find. * @param attributeLocalName The local name of the attribute to find. * @return The value of the specified attribute, or null if there is no such attribute. */ protected String getElementAttributeValue(final Element element, final String attributeNamespaceURI, final String attributeLocalName) { return XMLStyles.getXMLAttributeValue(element.getAttributes(), attributeNamespaceURI, attributeLocalName); //return the XML attribute value from the element's attributes } /** * Retrieves the parent element for the given element. * @param element The element for which a parent should be found. * @return The element's parent, or null if no parent could be found. */ protected Element getParentElement(final Element element) { final Element parentElement = element.getParentElement(); //get this element's parent return parentElement instanceof SectionElement ? null : parentElement; //return the parent element, unless we've reached the parent section element } /** * Determines the number of child elements the given element has. * @param element The parent element. * @return The number of child elements this element has. */ protected int getChildCount(final Element element) { return element.getElementCount(); //return the number of child elements } /** * Determines if the given indexed child of an element is an element. This version always returns true, as all Swing element children are also * elements. * @param element The parent element. * @param index The zero-based index of the child. * @return true if the the child of the element at the given index is an element. */ protected boolean isChildElement(final Element element, final int index) { return true; //there are no non-element children of elements } /** * Retrieves the given indexed child of an element. * @param element The parent element. * @param index The zero-based index of the child. * @return The child of the element at the given index. */ protected Element getChildElement(final Element element, final int index) { return element.getElement(index); //return the child element at the given index } /** * Retrieves all child text of the given element. * @param element The element for which text should be returned. * @return The text content of the element. */ protected String getElementText(final Element element) { try { return SwingText.getText(element); //return the text of the element } catch(BadLocationException badLocationException) { //we should never get a bad location exception throw (AssertionError)new AssertionError(badLocationException.getMessage()).initCause(badLocationException); } } /** * Imports style information into that already gathered for the given element. * @param element The element for which style information should be imported * @param cssStyle The style information to import. */ protected void importCSSStyle(final Element element, final CSSStyleDeclaration cssStyle) { final AttributeSet attributeSet = element.getAttributes(); //get the element's attributes CSSStyleDeclaration elementStyle = (XMLCSSStyleDeclaration)XMLCSSStyles.getXMLCSSStyle(attributeSet); //get this element's style if(elementStyle == null) { //if there is no existing style (usually the editor kit will have supplied one already to reduce the performance hit here) elementStyle = new XMLCSSStyleDeclaration(); //create an empty default style TODO use standard DOM classes if we can assert attributeSet instanceof MutableAttributeSet : "Attribute set not mutable"; XMLCSSStyles.setXMLCSSStyle((MutableAttributeSet)attributeSet, elementStyle); //put the style in the attributes } //TODO del Log.trace("style rule is of type: ", cssStyleRule.getClass().getName()); //TODO del importStyle(elementStyle, cssStyle); //import the style } } }