org.apache.fop.fo.FObj Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/* $Id: FObj.java 1894164 2021-10-12 13:51:54Z ssteiner $ */
package org.apache.fop.fo;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.apache.xmlgraphics.util.QName;
import org.apache.fop.apps.FOPException;
import org.apache.fop.fo.extensions.ExtensionAttachment;
import org.apache.fop.fo.flow.ChangeBar;
import org.apache.fop.fo.flow.Marker;
import org.apache.fop.fo.pagination.PageSequence;
import org.apache.fop.fo.properties.Property;
import org.apache.fop.fo.properties.PropertyMaker;
/**
* Base class for representation of formatting objects and their processing.
* All standard formatting object classes extend this class.
*/
public abstract class FObj extends FONode implements Constants {
/** the list of property makers */
private static final PropertyMaker[] PROPERTY_LIST_TABLE
= FOPropertyMapping.getGenericMappings();
/** pointer to the descendant subtree */
protected FONode firstChild;
/** pointer to the end of the descendant subtree */
protected FONode lastChild;
/** The list of extension attachments, null if none */
private List extensionAttachments;
/** The map of foreign attributes, null if none */
private Map foreignAttributes;
/** Used to indicate if this FO is either an Out Of Line FO (see rec)
* or a descendant of one. Used during FO validation.
*/
private boolean isOutOfLineFODescendant;
/** Markers added to this element. */
private Map markers;
private int bidiLevel = -1;
// The value of properties relevant for all fo objects
private String id;
private String layer;
// End of property values
private boolean forceKeepTogether;
/**
* Create a new formatting object.
*
* @param parent the parent node
*/
public FObj(FONode parent) {
super(parent);
// determine if isOutOfLineFODescendant should be set
if (parent != null && parent instanceof FObj) {
if (((FObj) parent).getIsOutOfLineFODescendant()) {
isOutOfLineFODescendant = true;
} else {
int foID = getNameId();
if (foID == FO_FLOAT || foID == FO_FOOTNOTE
|| foID == FO_FOOTNOTE_BODY) {
isOutOfLineFODescendant = true;
}
}
}
}
/** {@inheritDoc} */
public FONode clone(FONode parent, boolean removeChildren)
throws FOPException {
FObj fobj = (FObj) super.clone(parent, removeChildren);
if (removeChildren) {
fobj.firstChild = null;
}
return fobj;
}
/**
* Returns the PropertyMaker for a given property ID.
* @param propId the property ID
* @return the requested Property Maker
*/
public static PropertyMaker getPropertyMakerFor(int propId) {
return PROPERTY_LIST_TABLE[propId];
}
/** {@inheritDoc} */
public void processNode(String elementName, Locator locator,
Attributes attlist, PropertyList pList)
throws FOPException {
setLocator(locator);
pList.addAttributesToList(attlist);
if (!inMarker() || "marker".equals(elementName)) {
bind(pList);
}
warnOnUnknownProperties(attlist, elementName, pList);
}
private void warnOnUnknownProperties(Attributes attlist, String objName, PropertyList propertyList)
throws FOPException {
Map unknowns = propertyList.getUnknownPropertyValues();
for (Entry entry : unknowns.entrySet()) {
FOValidationEventProducer producer = FOValidationEventProducer.Provider.get(getUserAgent()
.getEventBroadcaster());
producer.warnOnInvalidPropertyValue(this, objName,
getAttributeNameForValue(attlist, entry.getValue(), propertyList), entry.getKey(), null,
getLocator());
}
}
private String getAttributeNameForValue(Attributes attList, Property value, PropertyList propertyList)
throws FOPException {
for (int i = 0; i < attList.getLength(); i++) {
String attributeName = attList.getQName(i);
String attributeValue = attList.getValue(i);
Property prop = propertyList.getPropertyForAttribute(attList, attributeName, attributeValue);
if (prop != null && prop.equals(value)) {
return attributeName;
}
}
return "unknown";
}
/**
* Create a default property list for this element.
* {@inheritDoc}
*/
protected PropertyList createPropertyList(PropertyList parent,
FOEventHandler foEventHandler) throws FOPException {
return getBuilderContext().getPropertyListMaker().make(this, parent);
}
/**
* Bind property values from the property list to the FO node.
* Must be overridden in all FObj subclasses that have properties
* applying to it.
* @param pList the PropertyList where the properties can be found.
* @throws FOPException if there is a problem binding the values
*/
public void bind(PropertyList pList) throws FOPException {
id = pList.get(PR_ID).getString();
layer = pList.get(PR_X_LAYER).getString();
}
/**
* {@inheritDoc}
* @throws FOPException FOP Exception
*/
public void startOfNode() throws FOPException {
if (id != null) {
checkId(id);
}
PageSequence pageSequence = getRoot().getLastPageSequence();
if (pageSequence != null && pageSequence.hasChangeBars()) {
startOfNodeChangeBarList = pageSequence.getClonedChangeBarList();
}
}
/**
* {@inheritDoc}
* @throws FOPException FOP Exception
*/
public void endOfNode() throws FOPException {
List endOfNodeChangeBarList = null;
PageSequence pageSequence = getRoot().getLastPageSequence();
if (pageSequence != null) {
endOfNodeChangeBarList = pageSequence.getClonedChangeBarList();
}
if (startOfNodeChangeBarList != null && endOfNodeChangeBarList != null) {
nodeChangeBarList = new LinkedList(endOfNodeChangeBarList);
nodeChangeBarList.retainAll(startOfNodeChangeBarList);
if (nodeChangeBarList.isEmpty()) {
nodeChangeBarList = null;
}
startOfNodeChangeBarList = null;
}
super.endOfNode();
}
/**
* Setup the id for this formatting object.
* Most formatting objects can have an id that can be referenced.
* This methods checks that the id isn't already used by another FO
*
* @param id the id to check
* @throws ValidationException if the ID is already defined elsewhere
* (strict validation only)
*/
private void checkId(String id) throws ValidationException {
if (!inMarker() && !id.equals("")) {
Set idrefs = getBuilderContext().getIDReferences();
if (!idrefs.contains(id)) {
idrefs.add(id);
} else {
getFOValidationEventProducer().idNotUnique(this, getName(), id, true, locator);
}
}
}
/**
* Returns Out Of Line FO Descendant indicator.
* @return true if Out of Line FO or Out Of Line descendant, false otherwise
*/
boolean getIsOutOfLineFODescendant() {
return isOutOfLineFODescendant;
}
/** {@inheritDoc}*/
protected void addChildNode(FONode child) throws FOPException {
if (child.getNameId() == FO_MARKER) {
addMarker((Marker) child);
} else {
ExtensionAttachment attachment = child.getExtensionAttachment();
if (attachment != null) {
/* This removes the element from the normal children,
* so no layout manager is being created for them
* as they are only additional information.
*/
addExtensionAttachment(attachment);
} else {
if (firstChild == null) {
firstChild = child;
lastChild = child;
} else {
if (lastChild == null) {
FONode prevChild = firstChild;
while (prevChild.siblings != null
&& prevChild.siblings[1] != null) {
prevChild = prevChild.siblings[1];
}
FONode.attachSiblings(prevChild, child);
} else {
FONode.attachSiblings(lastChild, child);
lastChild = child;
}
}
}
}
}
/**
* Used by RetrieveMarker during Marker-subtree cloning
* @param child the (cloned) child node
* @param parent the (cloned) parent node
* @throws FOPException when the child could not be added to the parent
*/
protected static void addChildTo(FONode child, FONode parent)
throws FOPException {
parent.addChildNode(child);
}
/** {@inheritDoc} */
public void removeChild(FONode child) {
FONode nextChild = null;
if (child.siblings != null) {
nextChild = child.siblings[1];
}
if (child == firstChild) {
firstChild = nextChild;
if (firstChild != null) {
firstChild.siblings[0] = null;
}
} else if (child.siblings != null) {
FONode prevChild = child.siblings[0];
prevChild.siblings[1] = nextChild;
if (nextChild != null) {
nextChild.siblings[0] = prevChild;
}
}
if (child == lastChild) {
if (child.siblings != null) {
lastChild = child.siblings[0];
} else {
lastChild = null;
}
}
}
/**
* Find the nearest parent, grandparent, etc. FONode that is also an FObj
* @return FObj the nearest ancestor FONode that is an FObj
*/
public FObj findNearestAncestorFObj() {
FONode par = parent;
while (par != null && !(par instanceof FObj)) {
par = par.parent;
}
return (FObj) par;
}
/**
* Check if this formatting object generates reference areas.
* @return true if generates reference areas
* TODO see if needed
*/
public boolean generatesReferenceAreas() {
return false;
}
/** {@inheritDoc} */
public FONodeIterator getChildNodes() {
if (hasChildren()) {
return new FObjIterator(this);
}
return null;
}
/**
* Indicates whether this formatting object has children.
* @return true if there are children
*/
public boolean hasChildren() {
return this.firstChild != null;
}
/**
* Return an iterator over the object's childNodes starting
* at the passed-in node (= first call to iterator.next() will
* return childNode)
* @param childNode First node in the iterator
* @return A FONodeIterator or null if childNode isn't a child of
* this FObj.
*/
public FONodeIterator getChildNodes(FONode childNode) {
FONodeIterator it = getChildNodes();
if (it != null) {
if (firstChild == childNode) {
return it;
} else {
while (it.hasNext()
&& it.next().siblings[1] != childNode) {
//nop
}
if (it.hasNext()) {
return it;
} else {
return null;
}
}
}
return null;
}
/**
* Notifies a FObj that one of it's children is removed.
* This method is subclassed by Block to clear the
* firstInlineChild variable in case it doesn't generate
* any areas (see addMarker()).
* @param node the node that was removed
*/
void notifyChildRemoval(FONode node) {
//nop
}
/**
* Add the marker to this formatting object.
* If this object can contain markers it checks that the marker
* has a unique class-name for this object and that it is
* the first child.
* @param marker Marker to add.
*/
protected void addMarker(Marker marker) {
String mcname = marker.getMarkerClassName();
if (firstChild != null) {
// check for empty childNodes
for (FONodeIterator iter = getChildNodes(); iter.hasNext();) {
FONode node = iter.next();
if (node instanceof FObj
|| (node instanceof FOText
&& ((FOText) node).willCreateArea())) {
getFOValidationEventProducer().markerNotInitialChild(this, getName(),
mcname, locator);
return;
} else if (node instanceof FOText) {
iter.remove();
notifyChildRemoval(node);
}
}
}
if (markers == null) {
markers = new HashMap();
}
if (!markers.containsKey(mcname)) {
markers.put(mcname, marker);
} else {
getFOValidationEventProducer().markerNotUniqueForSameParent(this, getName(),
mcname, locator);
}
}
/**
* @return true if there are any Markers attached to this object
*/
public boolean hasMarkers() {
return markers != null && !markers.isEmpty();
}
/**
* @return the collection of Markers attached to this object
*/
public Map getMarkers() {
return markers;
}
/** {@inheritDoc} */
protected String getContextInfoAlt() {
StringBuilder sb = new StringBuilder();
if (getLocalName() != null) {
sb.append(getName());
sb.append(", ");
}
if (hasId()) {
sb.append("id=").append(getId());
return sb.toString();
}
String s = gatherContextInfo();
if (s != null) {
sb.append("\"");
if (s.length() < 32) {
sb.append(s);
} else {
sb.append(s.substring(0, 32));
sb.append("...");
}
sb.append("\"");
return sb.toString();
} else {
return null;
}
}
/** {@inheritDoc} */
protected String gatherContextInfo() {
if (getLocator() != null) {
return super.gatherContextInfo();
} else {
FONodeIterator iter = getChildNodes();
if (iter == null) {
return null;
}
StringBuilder sb = new StringBuilder();
while (iter.hasNext()) {
FONode node = iter.next();
String s = node.gatherContextInfo();
if (s != null) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(s);
}
}
return (sb.length() > 0 ? sb.toString() : null);
}
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the "%block;" parameter entity
* as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
*
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isBlockItem(String nsURI, String lName) {
return (FO_URI.equals(nsURI)
&& ("block".equals(lName)
|| "table".equals(lName)
|| "table-and-caption".equals(lName)
|| "block-container".equals(lName)
|| "list-block".equals(lName)
|| "float".equals(lName)
|| isNeutralItem(nsURI, lName)));
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the "%inline;" parameter entity
* as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
*
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isInlineItem(String nsURI, String lName) {
return (FO_URI.equals(nsURI)
&& ("bidi-override".equals(lName)
|| "change-bar-begin".equals(lName)
|| "change-bar-end".equals(lName)
|| "character".equals(lName)
|| "external-graphic".equals(lName)
|| "instream-foreign-object".equals(lName)
|| "inline".equals(lName)
|| "inline-container".equals(lName)
|| "leader".equals(lName)
|| "page-number".equals(lName)
|| "page-number-citation".equals(lName)
|| "page-number-citation-last".equals(lName)
|| "basic-link".equals(lName)
|| ("multi-toggle".equals(lName)
&& (getNameId() == FO_MULTI_CASE
|| findAncestor(FO_MULTI_CASE) > 0))
|| ("footnote".equals(lName)
&& !isOutOfLineFODescendant)
|| isNeutralItem(nsURI, lName)));
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the "%block;" parameter entity
* or "%inline;" parameter entity
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isBlockOrInlineItem(String nsURI, String lName) {
return (isBlockItem(nsURI, lName) || isInlineItem(nsURI, lName));
}
/**
* Convenience method for validity checking. Checks if the
* incoming node is a member of the neutral item list
* as defined in Sect. 6.2 of the XSL 1.0 & 1.1 Recommendations
* @param nsURI namespace URI of incoming node
* @param lName local name (i.e., no prefix) of incoming node
* @return true if a member, false if not
*/
protected boolean isNeutralItem(String nsURI, String lName) {
return (FO_URI.equals(nsURI)
&& ("multi-switch".equals(lName)
|| "multi-properties".equals(lName)
|| "wrapper".equals(lName)
|| (!isOutOfLineFODescendant && "float".equals(lName))
|| "retrieve-marker".equals(lName)
|| "retrieve-table-marker".equals(lName)));
}
/**
* Convenience method for validity checking. Checks if the
* current node has an ancestor of a given name.
* @param ancestorID ID of node name to check for (e.g., FO_ROOT)
* @return number of levels above FO where ancestor exists,
* -1 if not found
*/
protected int findAncestor(int ancestorID) {
int found = 1;
FONode temp = getParent();
while (temp != null) {
if (temp.getNameId() == ancestorID) {
return found;
}
found += 1;
temp = temp.getParent();
}
return -1;
}
/**
* Clears the list of child nodes.
*/
public void clearChildNodes() {
this.firstChild = null;
}
/** @return the "id" property. */
public String getId() {
return id;
}
/** @return whether this object has an id set */
public boolean hasId() {
return (id != null && id.length() > 0);
}
/** @return the "layer" property. */
public String getLayer() {
return layer;
}
/** @return whether this object has an layer set */
public boolean hasLayer() {
return (layer != null && layer.length() > 0);
}
/** {@inheritDoc} */
public String getNamespaceURI() {
return FOElementMapping.URI;
}
/** {@inheritDoc} */
public String getNormalNamespacePrefix() {
return "fo";
}
/** {@inheritDoc} */
public boolean isBidiRangeBlockItem() {
String ns = getNamespaceURI();
String ln = getLocalName();
return !isNeutralItem(ns, ln) && isBlockItem(ns, ln);
}
/**
* Recursively set resolved bidirectional level of FO (and its ancestors) if
* and only if it is non-negative and if either the current value is reset (-1)
* or the new value is less than the current value.
* @param bidiLevel a non-negative bidi embedding level
*/
public void setBidiLevel(int bidiLevel) {
assert bidiLevel >= 0;
if ((this.bidiLevel < 0) || (bidiLevel < this.bidiLevel)) {
this.bidiLevel = bidiLevel;
if ((parent != null) && !isBidiPropagationBoundary()) {
FObj foParent = (FObj) parent;
int parentBidiLevel = foParent.getBidiLevel();
if ((parentBidiLevel < 0) || (bidiLevel < parentBidiLevel)) {
foParent.setBidiLevel(bidiLevel);
}
}
}
}
/**
* Obtain resolved bidirectional level of FO.
* @return either a non-negative bidi embedding level or -1
* in case no bidi levels have been assigned
*/
public int getBidiLevel() {
return bidiLevel;
}
/**
* Obtain resolved bidirectional level of FO or nearest FO
* ancestor that has a resolved level.
* @return either a non-negative bidi embedding level or -1
* in case no bidi levels have been assigned to this FO or
* any ancestor
*/
public int getBidiLevelRecursive() {
for (FONode fn = this; fn != null; fn = fn.getParent()) {
if (fn instanceof FObj) {
int level = ((FObj) fn).getBidiLevel();
if (level >= 0) {
return level;
}
}
if (isBidiInheritanceBoundary()) {
break;
}
}
return -1;
}
protected boolean isBidiBoundary(boolean propagate) {
return false;
}
private boolean isBidiInheritanceBoundary() {
return isBidiBoundary(false);
}
private boolean isBidiPropagationBoundary() {
return isBidiBoundary(true);
}
/**
* Add a new extension attachment to this FObj.
* (see org.apache.fop.fo.FONode for details)
*
* @param attachment the attachment to add.
*/
void addExtensionAttachment(ExtensionAttachment attachment) {
if (attachment == null) {
throw new NullPointerException(
"Parameter attachment must not be null");
}
if (extensionAttachments == null) {
extensionAttachments = new java.util.ArrayList();
}
if (log.isDebugEnabled()) {
log.debug("ExtensionAttachment of category "
+ attachment.getCategory() + " added to "
+ getName() + ": " + attachment);
}
extensionAttachments.add(attachment);
}
/** @return the extension attachments of this FObj. */
public List getExtensionAttachments() {
if (extensionAttachments == null) {
return Collections.EMPTY_LIST;
} else {
return extensionAttachments;
}
}
/** @return true if this FObj has extension attachments */
public boolean hasExtensionAttachments() {
return extensionAttachments != null;
}
/**
* Adds a foreign attribute to this FObj.
* @param attributeName the attribute name as a QName instance
* @param value the attribute value
*/
public void addForeignAttribute(QName attributeName, String value) {
/* TODO: Handle this over FOP's property mechanism so we can use
* inheritance.
*/
if (attributeName == null) {
throw new NullPointerException("Parameter attributeName must not be null");
}
if (foreignAttributes == null) {
foreignAttributes = new java.util.HashMap();
}
foreignAttributes.put(attributeName, value);
}
/** @return the map of foreign attributes */
public Map getForeignAttributes() {
if (foreignAttributes == null) {
return Collections.EMPTY_MAP;
} else {
return foreignAttributes;
}
}
/** {@inheritDoc} */
public String toString() {
return (super.toString() + "[@id=" + this.id + "]");
}
public boolean isForceKeepTogether() {
return forceKeepTogether;
}
public void setForceKeepTogether(boolean b) {
forceKeepTogether = b;
}
/** Basic {@link FONode.FONodeIterator} implementation */
public static class FObjIterator implements FONodeIterator {
private static final int F_NONE_ALLOWED = 0;
private static final int F_SET_ALLOWED = 1;
private static final int F_REMOVE_ALLOWED = 2;
private FONode currentNode;
private final FObj parentNode;
private int currentIndex;
private int flags = F_NONE_ALLOWED;
FObjIterator(FObj parent) {
this.parentNode = parent;
this.currentNode = parent.firstChild;
this.currentIndex = 0;
this.flags = F_NONE_ALLOWED;
}
/** {@inheritDoc} */
public FObj parent() {
return parentNode;
}
/** {@inheritDoc} */
public FONode next() {
if (currentNode != null) {
if (currentIndex != 0) {
if (currentNode.siblings != null
&& currentNode.siblings[1] != null) {
currentNode = currentNode.siblings[1];
} else {
throw new NoSuchElementException();
}
}
currentIndex++;
flags |= (F_SET_ALLOWED | F_REMOVE_ALLOWED);
return currentNode;
} else {
throw new NoSuchElementException();
}
}
/** {@inheritDoc} */
public FONode previous() {
if (currentNode.siblings != null
&& currentNode.siblings[0] != null) {
currentIndex--;
currentNode = currentNode.siblings[0];
flags |= (F_SET_ALLOWED | F_REMOVE_ALLOWED);
return currentNode;
} else {
throw new NoSuchElementException();
}
}
/** {@inheritDoc} */
public void set(FONode newNode) {
if ((flags & F_SET_ALLOWED) == F_SET_ALLOWED) {
if (currentNode == parentNode.firstChild) {
parentNode.firstChild = newNode;
} else {
FONode.attachSiblings(currentNode.siblings[0], newNode);
}
if (currentNode.siblings != null
&& currentNode.siblings[1] != null) {
FONode.attachSiblings(newNode, currentNode.siblings[1]);
}
if (currentNode == parentNode.lastChild) {
parentNode.lastChild = newNode;
}
} else {
throw new IllegalStateException();
}
}
/** {@inheritDoc} */
public void add(FONode newNode) {
if (currentIndex == -1) {
if (currentNode != null) {
FONode.attachSiblings(newNode, currentNode);
}
parentNode.firstChild = newNode;
currentIndex = 0;
currentNode = newNode;
if (parentNode.lastChild == null) {
parentNode.lastChild = newNode;
}
} else {
if (currentNode.siblings != null
&& currentNode.siblings[1] != null) {
FONode.attachSiblings(newNode, currentNode.siblings[1]);
}
FONode.attachSiblings(currentNode, newNode);
if (currentNode == parentNode.lastChild) {
parentNode.lastChild = newNode;
}
}
flags &= F_NONE_ALLOWED;
}
/** {@inheritDoc} */
public boolean hasNext() {
return (currentNode != null)
&& ((currentIndex == 0)
|| (currentNode.siblings != null
&& currentNode.siblings[1] != null));
}
/** {@inheritDoc} */
public boolean hasPrevious() {
return (currentIndex != 0)
|| (currentNode.siblings != null
&& currentNode.siblings[0] != null);
}
/** {@inheritDoc} */
public int nextIndex() {
return currentIndex + 1;
}
/** {@inheritDoc} */
public int previousIndex() {
return currentIndex - 1;
}
/** {@inheritDoc} */
public void remove() {
if ((flags & F_REMOVE_ALLOWED) == F_REMOVE_ALLOWED) {
parentNode.removeChild(currentNode);
if (currentIndex == 0) {
//first node removed
currentNode = parentNode.firstChild;
} else if (currentNode.siblings != null
&& currentNode.siblings[0] != null) {
currentNode = currentNode.siblings[0];
currentIndex--;
} else {
currentNode = null;
}
flags &= F_NONE_ALLOWED;
} else {
throw new IllegalStateException();
}
}
/** {@inheritDoc} */
public FONode last() {
while (currentNode != null
&& currentNode.siblings != null
&& currentNode.siblings[1] != null) {
currentNode = currentNode.siblings[1];
currentIndex++;
}
return currentNode;
}
/** {@inheritDoc} */
public FONode first() {
currentNode = parentNode.firstChild;
currentIndex = 0;
return currentNode;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy