7 Replies Latest reply on Aug 31, 2016 5:39 AM by Russ Ward

    Add an Element to an ElementRange

    frameexpert Level 4

      Here is a screenshot to illustrate my question:

      AddToElementRange.png

      I have an ElementRange as illustrated by the selection in the screenshot. I need to add the two follow li elements to the ElementRange because they have the same status attribute value ("new"). Give an ElementRange, how do I add the next sibling element to the ElementRange? I am content with adding them one at a time, if necessary. I have some ideas and will post some code, but I am trying to get a head-start by asking here. Thank you very much.

        • 1. Re: Add an Element to an ElementRange
          frameexpert Level 4

          Here is what I have working:

           

          function addNextSiblingToElementRange (element, doc) {
              
              var nextSibling = element.NextSiblingElement.NextSiblingElement;
              var elementRange = new ElementRange;
              
              if (nextSibling.NextSiblingElement.ObjectValid ()) {
                  elementRange.beg.parent = element.ParentElement;
                  elementRange.beg.child = element;
                  elementRange.end.parent = element.ParentElement;
                  elementRange.end.child = nextSibling.NextSiblingElement;
              }
              else {
                  elementRange.beg.parent = element.ParentElement;
                  elementRange.beg.child = element;
                  elementRange.beg.offset = 0;
                  elementRange.end.parent = element.ParentElement;
                  elementRange.end.offset = 0;
              }
          
              return elementRange;
          }
          

           

          Thanks to Russ for the original code. What I want to do now is to find the last consecutive sibling element that has the status="new" value. Given the selected element, I can use this xpath statement to get the next one:

           

          following-sibling::*[1][@status='new']
          

           

          I really want to get the last one, but I am not sure if there is a single xpath statement that I can use. At this point I can use a loop, which I am developing now.

          • 2. Re: Add an Element to an ElementRange
            frameexpert Level 4

            Here is my function to get the last consecutive sibling element that has the xpath statement.

             

            var doc = app.ActiveDoc;
            
            // The first selected element.
            var element = doc.ElementSelection.beg.child;
            alert (element.id);
            // Get the last consecutive element of the selected element that matches the xpath statement.
            element = getLastConsecutiveSibling (element, "following-sibling::*[1][@status='new']", doc);
            alert (element.id);
            
            function getLastConsecutiveSibling (element, xpath, doc) {
               
                var elements;
               
                do {
                    elements = getElements (xpath, element, doc);
                    if (elements.length) {
                        element = elements[0];
                    }
                }
                while (elements.length);
               
                return element;       
            }
            
            • 3. Re: Add an Element to an ElementRange
              frameexpert Level 4

              Here is the required getElements function that uses FrameSLT:

               

              function getElements (xpath, element, doc) {
                  
                  var elements = [], seq, id, contextId;
                  
                  contextId = element === 0 ? 0 : element.id;
                  
                  CallClient ("FrameSLT" , "SetAppParm---EC_ReturnIDType---UID");
                  seq = CallClient ("FrameSLT", "ParseXPath---" + xpath + "---False");
                  if (seq > 100) {
                      id = CallClient ("FrameSLT" , "FindNextNode---" + seq + "---" + doc.id + "---" + contextId + "---Main");
                      while (id > 100) {
                          elem = doc.GetUniqueObject (Constants.FO_Element, id);
                          if (elem.ObjectValid () === 1) {
                              elements.push (elem);
                          }
                          id = CallClient ("FrameSLT" , "FindNextNode---" + seq + "---" + doc.id + "---" + contextId + "---Main");
                      }
                  }
                  
                  return elements;    
              }
              
              
              • 4. Re: Add an Element to an ElementRange
                4everJang Level 3

                Hi Rick,

                 

                It will be much faster to just walk the tree and check the attribute values until you find one that does not have the required value. Running an Xpath everytime. Even a single CallClient will take more tine, and Russ' code probably does the same.

                 

                If your current last element is oLastElement, this is what you would need to run:

                 

                var oNext = oLastElement.NextSiblingElement;

                if( GetAttribute( oNext, "status" ) == "new" )

                {

                     var bFound = false;

                     while( oNext.NextSiblingElement.ObjectValid( ) && !bFound )

                     {

                          if( GetAttribute( oNext.NextSiblingElement, "status" ) == 'new' )

                               oNext = oNext.NextSiblingElement;

                          else

                               bFound = true;

                     }

                }

                 

                now your oNext contains the last element with the status 'new'.

                 

                function GetAttribute ( myElem, myAttrName )

                {

                     var attrs = myElem.Attributes;

                     var retval = "";

                     var i = 0;

                     var bFound = false;

                 

                    while ( i < attrs.length && !bFound )

                    {

                          if ( attrs[i].name == myAttrName )

                          {

                                 bFound = true;

                                 if ( attrs[i].values.length > 0 )

                                 {

                                      retval = attrs[i].values[0];

                                 }

                         }

                         i++;

                  }

                  return retval;

                }

                • 5. Re: Add an Element to an ElementRange
                  frameexpert Level 4

                  OK, let me restate my task. Given any element with a particular status attribute value, I want to make an element range containing all of the following consecutive elements with the same status attribute value. First, to isolate the elements that are the first element in a series of sibling elements (or the first with no ancestors), I am using this xpath statement with FrameSLT:

                   

                  //*[@status="new"][not(ancestor::*[@status="new"])][not(preceding-sibling::*[1][@status="new"])]
                  

                   

                  This makes sure that I don't process any elements that have ancestors with the same value, or elements that have immediate preceding sibling elements with the same value. For each of these elements, I will get the appropriate range by using the code below. NOTE: For the code below, I am testing with the "first" element being the currently selected element in the document. In the finished script, I will loop through the elements returned by the xpath statement above.

                   

                  #target framemaker
                  
                  var doc = app.ActiveDoc;
                  
                  // Start with the first selected element.
                  var element = doc.ElementSelection.beg.child;
                  // Get the last consecutive following sibling element that matches the xpath statement.
                  var lastElement = getLastConsecutiveSibling (element, "following-sibling::*[1][@status='new']", doc);
                  // Now that we have the last element (which may actually be the same as the first), get the element range.
                  var elementRange = getConsecutiveElementRange (element, lastElement, doc);
                  // Select the element range.
                  doc.ElementSelection = elementRange;
                  
                  function getLastConsecutiveSibling (element, xpath, doc) {
                      
                      var elements;
                      
                      do {
                          elements = getElements (xpath, element, doc);
                          if (elements.length) {
                              element = elements[0];
                          }
                      }
                      while (elements.length);
                      
                      return element;        
                  }
                  
                  function getConsecutiveElementRange (firstElement, lastElement, doc) {
                      
                      var nextSibling = lastElement.NextSiblingElement;
                      var elementRange = new ElementRange;
                      
                      if (lastElement.NextSiblingElement.ObjectValid ()) {
                          elementRange.beg.parent = element.ParentElement;
                          elementRange.beg.child = element;
                          elementRange.end.parent = element.ParentElement;
                          elementRange.end.child = nextSibling;
                      }
                      else {
                          elementRange.beg.parent = element.ParentElement;
                          elementRange.beg.child = element;
                          elementRange.beg.offset = 0;
                          elementRange.end.parent = element.ParentElement;
                          elementRange.end.offset = 0;
                      }
                  
                      return elementRange;
                      
                  }
                  
                  function getElements (xpath, element, doc) {
                      
                      var elements = [], seq, id, contextId;
                      
                      contextId = element === 0 ? 0 : element.id;
                      
                      CallClient ("FrameSLT" , "SetAppParm---EC_ReturnIDType---UID");
                      seq = CallClient ("FrameSLT", "ParseXPath---" + xpath + "---False");
                      if (seq > 100) {
                          id = CallClient ("FrameSLT" , "FindNextNode---" + seq + "---" + doc.id + "---" + contextId + "---Main");
                          while (id > 100) {
                              elem = doc.GetUniqueObject (Constants.FO_Element, id);
                              if (elem.ObjectValid () === 1) {
                                  elements.push (elem);
                              }
                              id = CallClient ("FrameSLT" , "FindNextNode---" + seq + "---" + doc.id + "---" + contextId + "---Main");
                          }
                      }
                      
                      return elements;    
                  }
                  

                   

                  My only wish list item is to find a single xpath statement that would replace the getLastConsecutiveSibling function, but that is not a big deal. I am fascinated by the xpath language and always wonder if certain things can be done with it.

                   

                  Any feedback or suggestions are appreciated.

                   

                  -Rick

                  • 6. Re: Add an Element to an ElementRange
                    frameexpert Level 4

                    Hi Jang, All good points, thank you. I want to use xpath here, because there will be some variety in the attribute/value combinations that I will need to find. So I am opting to use FrameSLT and xpath because this will give me some flexibility in isolating exactly what I need, even at the cost of some efficiency. -Rick

                    • 7. Re: Add an Element to an ElementRange
                      Russ Ward Level 4

                      Hi Rick,

                       

                      Fun challenge. Thanks for giving me the chance to stew over what FrameSLT can do. And, my apologies for the delay in response.

                       

                      I think I figured it out, but you will need to be the judge. Given some context element with @status="new", I believe this will get the last consecutive sibling with the same status:

                       

                      .[following-sibling::*[1 and @status="new"]]/following-sibling::*[@status="new" and not(following-sibling::*[1 and @status="new"])]

                       

                      ...or it will get nothing if there are no qualifying siblings. Literally, this expression says "first check to see if there is at least one adjacent @status="new" sibling -and then- "match me if I'm a sibling with @status="new" -and- I myself do not have the same sibling immediately following (position() = 1).

                       

                      Note that this expression could match all "last elements" on a branch if there are multiple independent groups. So you should just call it once for each original context element, then don't forget to call ResetSequence to initialize the navigator for the expression. Or, ParseXPath will also reset the sequence with no additional expense. If the expression is already parsed, a ParseXPath call basically just invokes a ResetSequence and returns you the same sequence number.

                       

                      Hope this helps.

                      Russ