18 Replies Latest reply on Aug 25, 2016 11:19 AM by frameexpert

    End Element of a ElementRange

    frameexpert Level 4

      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

        • 1. Re: End Element of a ElementRange
          4everJang Level 3

          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.

          • 2. Re: End Element of a ElementRange
            frameexpert Level 4

            Hi Jang,

             

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

             

            Rick

            • 3. Re: End Element of a ElementRange
              Russ Ward Level 4

              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;
              }
              
              
              • 4. Re: End Element of a ElementRange
                frameexpert Level 4

                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

                • 5. Re: End Element of a ElementRange
                  4everJang Level 3

                  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

                  • 6. Re: End Element of a ElementRange
                    frameexpert Level 4

                    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

                    • 7. Re: End Element of a ElementRange
                      Russ Ward Level 4

                      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

                      • 8. Re: End Element of a ElementRange
                        4everJang Level 3

                        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

                        • 9. Re: End Element of a ElementRange
                          frameexpert Level 4

                          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;
                          }
                          
                          • 10. Re: End Element of a ElementRange
                            4everJang Level 3

                            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 ?

                            • 11. Re: End Element of a ElementRange
                              frameexpert Level 4

                              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.

                              • 12. Re: End Element of a ElementRange
                                Russ Ward Level 4

                                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;
                                }
                                
                                
                                • 13. Re: End Element of a ElementRange
                                  frameexpert Level 4

                                  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.

                                  • 14. Re: End Element of a ElementRange
                                    frameexpert Level 4

                                    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.

                                    • 15. Re: End Element of a ElementRange
                                      Russ Ward Level 4

                                      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.

                                      • 16. Re: End Element of a ElementRange
                                        4everJang Level 3

                                        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.

                                        • 17. Re: End Element of a ElementRange
                                          frameexpert Level 4

                                          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.

                                          • 18. Re: End Element of a ElementRange
                                            frameexpert Level 4

                                            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.