• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
0

End Element of a ElementRange

Community Expert ,
Aug 23, 2016 Aug 23, 2016

Copy link to clipboard

Copied

First, here is a picture to illustrate what I am trying to do:

Boundaries.png

Given an ElementRange, I am trying to find the upper and lower boundaries of that range, as in the FP_LocY values (show visually in yellow). The upper value is easy, because I can get the top element in the ElementRange. But I am not sure the best approach for getting the lower value. Any help will be appreciated. Thanks.

-Rick

TOPICS
Scripting

Views

757

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Mentor , Aug 25, 2016 Aug 25, 2016

Rick, I think I follow you, but I'm not 100% sure. Here is a revision to that function, that first gets the last sibling element in the selection, then walks down the last branch(es) to the furthest extent. If this is not what you are looking for, then I think it might be much more complicated than I understand. Also, by the way, FrameSLT does not support that XPath expression. I don't believe there is an equivalent, but I'd really have to think about it.

Also, you certainly understand this, but

...

Votes

Translate

Translate
Advocate ,
Aug 23, 2016 Aug 23, 2016

Copy link to clipboard

Copied

Hi Rick,

If I understand correctly, you are looking for physical (i.e. screen) locations rather than logical or stuctural locations.

Use the TextRange of the last element in your range to find the last paragraph in that last element. Then select the next paragraph in the same flow and you have the lower LocY boundary of your selection. Of course that next paragraph might be on a new page.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 23, 2016 Aug 23, 2016

Copy link to clipboard

Copied

Hi Jang,

Thanks. I was looking for a reliable way to get the last element in the selection.

Rick

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
Aug 24, 2016 Aug 24, 2016

Copy link to clipboard

Copied

Hi Rick,

Piece of cake! Here is a function that returns the last element in the selection. Note that it only works when contiguous siblings are selected, not with elements on different branches selected (like when you select a table column). That takes more work. I put an alert in there so you could see it work.

Russ

function getLastSelectedElement(doc)

{

   var er = doc.ElementSelection;

  

   var lastElem;

   var elem = er.beg.child;

   while(elem.ObjectValid() && elem.id != er.end.child.id)

   {

      alert(elem.ElementDef.Name);

      lastElem = elem;

      elem = elem.NextSiblingElement;

   }

   return lastElem;

}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 24, 2016 Aug 24, 2016

Copy link to clipboard

Copied

Hi Russ, This works BUT I was really hoping to get the <p> element. In addition, if there is a single element (with children) selected, it returns the single element. I am looking to get the last element in the selection at the deepest level. Maybe there is an xpath statement I can use with FrameSLT. Thanks, Rick

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Aug 24, 2016 Aug 24, 2016

Copy link to clipboard

Copied

Rick,

The end of the text range is the far boundary of the last child element, which is by definition beyond the end of the last child element. In your example, if you find the end of the text range of the last <li> you will be one position beyond the end of the last <p>. Both of those end boundaries are inside the same paragraph.

Ciao

Jang

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Rick, OK. That does make things more complicated. Jang might be on to something but I would still tend to rely on the structure tree, rather than text ranges.  I think I know how to tweak that function just a little to get what you want, but I have one important question... what if the "last" selected element is a text node? Do you want that, or just the last tagged node?

Russ

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Hi Russ, I could use this xpath statement:

(//*)[last()]

This works in an XML document, but not with FrameSLT. I wonder if this is just a limitation of FrameSLT. Thanks.

-Rick

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Hi Rick,

If you look at the structured document with element boundaries switched on, you will see that the textrange of the last <li> does include all of the child elements inside that <li>. This means that the text location of the end boundary of that <li> is in the same paragraph object as that of the last <p> (or anything else) inside that <li>.

There is no need to make things more complicated than they already are. Unless there is something else you are trying to do here.

Ciao

Jang

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Here is some back story on what I am trying to do. I need to draw pseudo change bars in the margin next to elements with a particular attribute, or alternatively, next to an element selection. So I need to find the "top" and "bottom" elements in the range. Then I calculate their LocY values so I can determine the height of the change bars. In experimenting, I have found some strange issues when a single element with children is selected. In this case, the most accurate results come when I use the first child and the last element in the selection to get my values. Thus, in the screenshot, if it was the entire ul element that was selected, I would want the first li and the very last p in the branch to get the correct values. So I have come up with this somewhat convoluted code, which seems to satisfy any combination of element ranges. Of course, I still have to deal with selections that cross text columns or pages, but that is the next task to tackle.

#target framemaker

var doc = app.ActiveDoc;

var elementRange = doc.ElementSelection;

var elements = getTopAndBottomElements (elementRange);

alert (elements.top.ElementDef.Name);

alert (elements.bottom.ElementDef.Name);

function getTopAndBottomElements (elementRange) {

  

    var elements = {}, topElement, bottomElement, lastElement;

  

    topElement = elementRange.beg.child;

    bottomElement = getLastSiblingElement (elementRange);

  

    if (topElement.id === bottomElement.id) { // Single element selected.

        if (topElement.FirstChildElement.ObjectValid () === 1) {

            elements.top = topElement.FirstChildElement;

            elements.bottom = getLastElementInBranch (topElement);

            return elements;

        }

        else { // No first child, just a single element.

            elements.top = elements.bottom = topElement;

            return elements;

        }

    }

    else { // Consecutive siblings selected.

        elements.top = topElement;

        lastElement = getLastSiblingElement (elementRange);

        if (lastElement.ObjectValid () === 1) {

            elements.bottom = getLastElementInBranch (lastElement);

        }

        return elements;

    }  

}

function getLastSiblingElement (elementRange)  {

  

    var lastElement, element;

    element = elementRange.beg.child;

    while (element.ObjectValid () && element.id !== elementRange.end.child.id)  {

        lastElement = element;

        element = element.NextSiblingElement;

    }

    return lastElement;

}

function getLastElementInBranch (element) {

  

    var elements, lastElem = 0;

  

    elements = getAllElements (element, []);

    if (elements.length) {

        lastElem = elements[elements.length - 1];

    }

    return lastElem;

}

function getAllElements (element, elements) {

    var elements2;

  

    if (element.ElementDef.ObjectValid ()) {

        elements.push(element);

    }

    element2 = element.FirstChildElement;

    while (element2.ObjectValid ()) {

        elements = getAllElements (element2, elements);

        element2 = element2.NextSiblingElement;

    }

    return elements;

}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

The LocY of the last element in the selection is still not the exact location of the end of your custom change bar, though. You would have to add the height of that last element, which may be a tricky thing to do again. If you find the next element beyond your selection and pick up the LocY from there, you then only need to determine whether that paragraph is on a new page and, if not, take its LocY minus the offset above that paragraph. I would expect that to give a better result than working your way down from a LocY that points to the top of an element. Makes sense ?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Once I get the top and bottom elements, then I can get the top and bottom paragraphs and make my LocY calculations from there. In my preliminary tests, this seems to work fine. I have to start with elements, though, because they have the attribute values that I need to check for.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Rick, I think I follow you, but I'm not 100% sure. Here is a revision to that function, that first gets the last sibling element in the selection, then walks down the last branch(es) to the furthest extent. If this is not what you are looking for, then I think it might be much more complicated than I understand. Also, by the way, FrameSLT does not support that XPath expression. I don't believe there is an equivalent, but I'd really have to think about it.

Also, you certainly understand this, but let me note for others that this function is missing lots of important error handling. It is only robust if the input is exactly as expected (ie, a valid document with a valid element selection).

Russ

function getLastSelectedElement(doc)

{

  var er = doc.ElementSelection;

  

  var lastElem;

  var tempLastElem;

  var elem = er.beg.child;

  while(elem.ObjectValid() && elem.id != er.end.child.id)

  {

     //alert(elem.ElementDef.Name);

     tempLastElem = elem;

     elem = elem.NextSiblingElement;

  }

  while(tempLastElem.ObjectValid())

  {

    lastElem = tempLastElem;

    tempLastElem = tempLastElem.LastChildElement;

  }

   return lastElem;

}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Yes, that's what I am trying to do. I like the way you combined my two functions into a single one, although in other scripts it may be useful to have one or the other. Yes, I did try that xpath expression with FrameSLT and it give me a syntax error. I tried it in an XML editor and it worked. But I don't mind traversing the tree since it is usually not very deep anyway. Thanks to you and Jang for the help.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

One more thing: I probably wouldn't pass in the document object, but an element range. That way, I could use it with the document's ElementSelection or I could pass in my own ElementRange.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Mentor ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

One note about walking through the tree... I have found it to be incredibly fast. You can step through every element in a very large tree in an instant. So, very little cost to just use the DOM approach and hop from node to node.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

LATEST

It's good to know that walking the tree is pretty fast. I do like to use FrameSLT/XPath, though, when I am looking for specific element/attribute combinations.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

Russ mentioned that the script might get wrong results when the structure is invalid. It is pretty easy to determine the validity of an element and incorporate that into the script. If you want I can dig out a piece of code that does this. Also, I am not sure whethet even an invalid structure would break the code.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 25, 2016 Aug 25, 2016

Copy link to clipboard

Copied

By the time I get to this code, I will have done some checking to make sure I pass in a valid ElementRange. Thanks anyway.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines