Configuring Extensions

 Configuring a Link target reference finder

The link target reference finder represents the support for finding references from links which indicate specific elements inside an XML document. This support will only be available if a schema is associated with the document type.

If you do not define a custom link target reference finder, the DefaultElementLocatorProvider implementation will be used by default. The interface which should be implemented for a custom link target reference finder is ro.sync.ecss.extensions.api.link.ElementLocatorProvider. As an alternative, the ro.sync.ecss.extensions.commons.DefaultElementLocatorProvider implementation can also be extended.

The used ElementLocatorProvider will be queried for an ElementLocator when a link location must be determined(clicking on a link). Then, to find the corresponding(linked) element, the obtained ElementLocator will be queried for each element from the document.

 The DefaultElementLocatorProvider implementation

The DefaultElementLocatorProvider implementation offers support for the most common types of links:

  • links based on ID attribute values

  • XPointer element() scheme

The method getElementLocator determines what ElementLocator should be used. In the default implementation it checks if the link is an XPointer element() scheme otherwise it assumes it is an ID. A non-null IDTypeVerifier will always be provided if a schema is associated with the document type.

The link string argument is the "anchor" part of the of the URL which is composed from the value of the link property specified for the link element in the CSS.

public ElementLocator getElementLocator(IDTypeVerifier idVerifier, String link) {
  ElementLocator elementLocator = null;
  try {
    if(link.startsWith("element(")){
      // xpointer element() scheme
      elementLocator = new XPointerElementLocator(idVerifier, link);
    } else {
      // Locate link element by ID
      elementLocator = new IDElementLocator(idVerifier, link);
    }
  } catch (ElementLocatorException e) {
    logger.warn("Exception when create element locator for link: " 
        + link + ". Cause: " + e, e);
  }
  return elementLocator;
}
 The XPointerElementLocator implementation

The XPointerElementLocator is an implementation of the abstract class ro.sync.ecss.extensions.api.link.ElementLocator for links that have one of the following XPointer element() scheme patterns:

element(elementID)

locate the element with the specified id

element(/1/2/3)

A child sequence appearing alone identifies an element by means of stepwise navigation, which is directed by a sequence of integers separated by slashes (/); each integer n locates the nth child element of the previously located element.

element(elementID/3/4)

A child sequence appearing after a NCName identifies an element by means of stepwise navigation, starting from the element located by the given name.

The constructor separates the id/integers which are delimited by slashes(/) into a sequence of identifiers(an XPointer path). It will also check that the link has one of the supported patterns of the XPointer element() scheme.

public XPointerElementLocator(IDTypeVerifier idVerifier, String link)
                      throws ElementLocatorException {
  super(link);
  this.idVerifier = idVerifier;

  link = link.substring("element(".length(), link.length() - 1);

  StringTokenizer stringTokenizer = new StringTokenizer(link, "/", false);
  xpointerPath = new String[stringTokenizer.countTokens()];
  int i = 0;
  while (stringTokenizer.hasMoreTokens()) {
    xpointerPath[i] = stringTokenizer.nextToken();
    boolean invalidFormat = false;
    
    // Empty xpointer component is not supported
    if(xpointerPath[i].length() == 0){
      invalidFormat = true;
    }
    
    if(i > 0){
      try {
        Integer.parseInt(xpointerPath[i]);
      } catch (NumberFormatException e) {
        invalidFormat = true;
      }
    }

    if(invalidFormat){
      throw new ElementLocatorException(
        "Only the element() scheme is supported when locating XPointer links. "
        + "Supported formats: element(elementID), element(/1/2/3), 
              element(elemID/2/3/4).");
    }
    i++;
  }

  if(Character.isDigit(xpointerPath[0].charAt(0))){
    // This is the case when xpointer have the following pattern /1/5/7
    xpointerPathDepth = xpointerPath.length;
  } else {
    // This is the case when xpointer starts with an element ID
    xpointerPathDepth = -1;
    startWithElementID  = true;
  }
}

The method startElement will be invoked at the beginning of every element in the XML document(even when the element is empty). The arguments it takes are

uri

the namespace URI, or the empty string if the element has no namespace URI or if namespace processing is disabled

localName

the local name of the element

qName

the qualified name of the element

atts

the attributes attached to the element. If there are no attributes, it will be empty.

The method returns true if the processed element is found to be the one indicated by the link.

The XPointerElementLocator implementation of the startElement will update the depth of the current element and keep the index of the element in its parent. If the xpointerPath starts with an element ID then the current element ID is verified to match the specified ID. If this is the case the depth of the XPointer is updated taking account of the depth of the current element.

If the XPointer path depth is the same as the current element depth then the kept indices of the current element path are compared to the indices in the XPointer path. If all of them match then the element has been found.

public boolean startElement(String uri, String localName, 
        String name, Attr[] atts) {
  boolean linkLocated = false;
  // Increase current element document depth
  startElementDepth ++;
  
  if (endElementDepth != startElementDepth) {
    // The current element is the first child of the parent
    currentElementIndexStack.push(new Integer(1));
  } else {
    // Another element in the parent element
    currentElementIndexStack.push(new Integer(lastIndexInParent + 1));
  }
  
  if (startWithElementID) {
    // This the case when xpointer path starts with an element ID.
    String xpointerElement = xpointerPath[0];
    for (int i = 0; i < atts.length; i++) {
      if(xpointerElement.equals(atts[i].getValue())){
        if(idVerifier.hasIDType(
            localName, uri, atts[i].getQName(), atts[i].getNamespace())){
          xpointerPathDepth = startElementDepth + xpointerPath.length - 1;
          break;
        }
      }
    }
  }
      
  if (xpointerPathDepth == startElementDepth){
    // check if xpointer path matches with the current element path
    linkLocated = true;
    try {        
      int xpointerIdx = xpointerPath.length - 1;
      int stackIdx = currentElementIndexStack.size() - 1;
      int stopIdx = startWithElementID ? 1 : 0;
      while (xpointerIdx >= stopIdx && stackIdx >= 0) {
        int xpointerIndex = Integer.parseInt(xpointerPath[xpointerIdx]);
        int currentElementIndex = ((Integer)currentElementIndexStack.get(stackIdx)).intValue();
        if(xpointerIndex != currentElementIndex) {
          linkLocated = false;
          break;
        }
        
        xpointerIdx--;
        stackIdx--;
      }

    } catch (NumberFormatException e) {
      logger.warn(e,e);
    }
  }
  return linkLocated;
}

The method endElement will be invoked at the end of every element in the XML document(even when the element is empty).

The XPointerElementLocator implementation of the endElement updates the depth of the current element path and the index of the element in its parent.

public void endElement(String uri, String localName, String name) {
  endElementDepth = startElementDepth;
  startElementDepth --;
  lastIndexInParent = ((Integer)currentElementIndexStack.pop()).intValue();
}
 The IDElementLocator implementation

The IDElementLocator is an implementation of the abstract class ro.sync.ecss.extensions.api.link.ElementLocator for links that use an id.

The constructor only assigns field values and the method endElement is empty for this implementation.

The method startElement checks each of the element's attribute values and when one matches the link, it considers the element found if one of the following conditions is satisfied:

  • the qualified name of the attribute is xml:id

  • the attribute is of type ID

The type of the attribute is checked with the help of the method IDTypeVerifier.hasIDType.

public boolean startElement(String uri, String localName, 
        String name, Attr[] atts) {
  boolean elementFound = false;
  for (int i = 0; i < atts.length; i++) {
    if (link.equals(atts[i].getValue())) {
      if("xml:id".equals(atts[i].getQName())) {
        // xml:id attribute
        elementFound = true;          
      } else {
        // check if attribute has ID type
        String attrLocalName = 
          ExtensionUtil.getLocalName(atts[i].getQName());
        String attrUri = atts[i].getNamespace();
        if (idVerifier.hasIDType(localName, uri, attrLocalName, attrUri)) {
          elementFound = true;
        }
      }
    }
  }
  
  return elementFound;
}
 Creating a customized link target reference finder

If you need to create a custom link target reference finder you can do so by following these steps.

  1. Create a new Java project, in your IDE.

    Create the lib directory in the Java project directory and copy in it the oxygen.jar file from the {oXygen_installation_directory}/lib directory.

  2. Create the class which will implement the ro.sync.ecss.extensions.api.link.ElementLocatorProvider interface. As an alternative, your class could extend ro.sync.ecss.extensions.commons.DefaultElementLocatorProvider, the default implementation.

    As a start point you can use the source code of the DefaultElementLocatorProvider implementation which is found in the Example Files Listings, the Java Files section. There you will also find the implementations for XPointerElementLocator and IDElementLocator.

  3. Package the compiled class into a jar file.

  4. Copy the jar file into the frameworks/sdf directory.

  5. Add the jar file to the Author class path.

  6. In the Extensions tab from the document type editing dialog, register the Java class by pressing the Choose button and select from the displayed dialog the name of your custom class.