Как отфильтровать элементы по их расстоянию до начала данного элемента предка? - PullRequest
0 голосов
/ 31 мая 2018

Минимальный пример: ожидаемое поведение определяется тестами Жасмин:

$(document).ready(function() {
  function thereIsImportantContent(id) {
    return $(id).find("strong").filter(function() {
      var index = $(id).text().indexOf($(this).text());
      return 0 <= index && index <= 20;
    }).length > 0;
  }

  // specs code
  describe("thereIsImportantContent", function() {

    it("accept strong near head", function() {
      expect(thereIsImportantContent($("#test_case_1")[0])).toBeTruthy();
    });

    it("accept strong near head with children", function() {
      expect(thereIsImportantContent($("#test_case_2")[0])).toBeTruthy();
    });

    it("accept wrapped strong near head", function() {
      expect(thereIsImportantContent($("#test_case_3")[0])).toBeTruthy();
    });

    it("reject strong further down", function() {
      expect(thereIsImportantContent($("#test_case_4")[0])).toBeFalsy();
    });

    it("reject strong further down with copies near head", function() {
      expect(thereIsImportantContent($("#test_case_5")[0])).toBeFalsy();
    });
  });

  // load jasmine htmlReporter
  (function() {
    var env = jasmine.getEnv();
    env.addReporter(new jasmine.HtmlReporter());
    env.execute();
  }());
});
container {
  display: none;
}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css">
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<container id="test_case_1">
  <!-- strong content at the beginning -> accept -->
  <p>Some <strong>content</strong></p>
  ...
  <p>other text</p>
  ...
  <p>Hey look: <strong>content</strong>!</p>
</container>

<container id="test_case_2">
  <!-- handle strong with children correctly -->
  <strong>Hey look: <span> content!</span></strong>
</container>

<container id="test_case_3">
  <p>Test</p>
  <p>Hey <strong>content!</strong></p>
</container>

<container id="test_case_4">
  <p>Something</p>
  ...
  <p>other text</p>
  ...
  <!-- strong content but located further down -> reject -->
  <p>Hey look: <strong>content</strong>!</p>
</container>

<container id="test_case_5">
  <!-- same text as in strong below triggering false accept -->
  <p>Some content</p>
  ...
  <p>other text</p>
  ...
  <!-- strong content but located further down -> should eject -->
  <p>Hey look: <strong>content</strong>!</p>
</container>

Мой пример использования: как часть пользовательского сценария, который пытается найти потенциальные заголовки сайта:

Предполагая, что у меня есть веб-сайт, который выглядит следующим образом:

<container>
  <p>Some <strong>content</strong></p>
  ...
  <p>other text</p>
  ...
  <p>Hey look: <strong>content</strong>!</p>
</container>

Я ищу способ найти важные элементы, такие как (например, h1, h2, strong, ...), которые визуально находятся рядом сначало видимого текста.

Выше код выглядит для пользователя примерно так:

Некоторый контент
...
другой текст
...
Эй, смотри: content !

Мой нынешний подход - оценить container.text().indexOf(elementOfIntrest.text()) и использовать только их с низким индексом ...

container.find("strong").slice(0,10).filter(function () {
    var index = container.text().indexOf($(this).text());
    console.log("Testing: " + $(this).text(), " index: " + index);
    return 0 <= index && index <= 50
});

Но я понял, что это работает только в том случае, если важный контент отсутствует в обычном тексте на более раннем этапе.

Например:

<container>
   <p>Some content</p>  <---position where the text "content" was found and
                             ^                             wrongly accepted       
   ...                       |Potentially important  
   <p>really long text</p>   |Element with text "content"
   ...                       |should be ignored as its far away
                             |from the start of the text
   <p>Hey look: <strong>content</strong>!</p>
</container>

indexOf находит «контент» из сильного элемента во второй строке и принимает его.


В: как эффективно фильтровать элементы HTML по их расстоянию доначало заданного элемента-предка, считается в символах?

Ответы [ 3 ]

0 голосов
/ 01 июня 2018

Я понял, что подошел к этой проблеме с неправильного направления и теперь нашел следующее решение:

  1. Обход DOM с использованием поиска по глубине и вычисление длины уже обработанного текста путем накопления element.textConent.lengthиз детей.
  2. Продолжайте до тех пор, пока не останется ни одного элемента или пока текст не достигнет заданного ограничения длины.
  3. При этом кэшируйте все отсканированные элементы, которые соответствуют заданному имени тега / другим критериям

$(document).ready(function() {
  function findChildrenNearElementHead(element, selector, maxDistance = 15) {
    /* length of already processed text 
   (thats the number of characters the user has already read when he arives at the child element in question) */
    var curLen = 0;

    /* current depth in the DOM relative to the container element
    if negative we finised processing all elements in the container and shoudl stop */
    var depth = 0;

    // look for children that match this selector
    selector = selector.toUpperCase().split(",");

    // the result
    var candidates = [];

    // traverse complete DOM in container in pre-order
    while (curLen < maxDistance && depth >= 0) {
      // if element matches selector
      if (selector.indexOf(element.tagName) > -1) {
        // add element to result array
        candidates.push({
          "element": element,
          "index": curLen
        });

        // increase current text length by length of element
        curLen += element.textContent.length;
      } else if (element.firstElementChild) {
        /* if this element is not matched by the selector 
        and has children, dive in and look there for more elements */

        // begin with the first child element
        element = element.firstElementChild;

        // increase depth 
        depth += 1;

        // skip rest of current loop iteration
        continue;
      }

      // increase current text length by length of element
      curLen += element.textContent.length;

      // element has no children, has it siblings?
      if (element.nextElementSibling) {
        // yes it has -> continiue there
        element = element.nextElementSibling;

        /* element has no siblings
        go one layer up to parent and look there for siblings */
      } else if (element.parentElement.nextElementSibling) {
        // select next sibling of parent as active element
        element = element.parentElement.nextElementSibling;

        // descrease depth as we just moved one layer up
        depth -= 1;
      } else {

        // no children, no siblings, nothing to do
        break;
      }
    }

    return candidates;
  }

  function thereIsImportantContent(element) {
    return findChildrenNearElementHead(element, "h1,h2,strong").length > 0;
  }

  // specs code
  describe("findChildrenNearElementHead", function() {

    it("accept strong near head", function() {
      expect(thereIsImportantContent($("#test_case_1")[0])).toBeTruthy();
    });

    it("accept strong near head with children", function() {
      expect(thereIsImportantContent($("#test_case_2")[0])).toBeTruthy();
    });

    it("accept wrapped strong near head", function() {
      expect(thereIsImportantContent($("#test_case_3")[0])).toBeTruthy();
    });

    it("reject strong further down", function() {
      expect(thereIsImportantContent($("#test_case_4")[0])).toBeFalsy();
    });

    it("reject strong further down with copies near head", function() {
      expect(thereIsImportantContent($("#test_case_5")[0])).toBeFalsy();
    });
  });

  // load jasmine htmlReporter
  (function() {
    var env = jasmine.getEnv();
    env.addReporter(new jasmine.HtmlReporter());
    env.execute();
  }());
});
container {
  display: none;
}
<link href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<container id="test_case_1">
  <!-- strong content at the beginning -> accept -->
  <p>Some <strong>content</strong></p>
  ...
  <p>other text</p>
  ...
  <p>Hey look: <strong>content</strong>!</p>
</container>

<container id="test_case_2">
  <!-- handle strong with children correctly -->
  <strong>Hey look: <span> content!</span></strong>
</container>

<container id="test_case_3">
  <p>Test</p>
  <p>Hey <strong>content!</strong></p>
</container>

<container id="test_case_4">
  <p>Something</p>
  ...
  <p>other text</p>
  ...
  <!-- strong content but located further down -> reject -->
  <p>Hey look: <strong>content</strong>!</p>
</container>

<container id="test_case_5">
  <!-- same text as in strong below triggering false accept -->
  <p>Some content</p>
  ...
  <p>other text</p>
  ...
  <!-- strong content but located further down -> should eject -->
  <p>Hey look: <strong>content</strong>!</p>
</container>

Копия Plunker: https://embed.plnkr.co/YjLVxVHAWj0kDhoceRGK/


Хотя это именно то, что я хочу, яЯ не удовлетворен количеством кода, учитывая, что у нас есть библиотеки, такие как jQuery.

Я надеюсь, что кто-то может придумать более компактный способ сделать это.

0 голосов
/ 03 июня 2018

Если я правильно понимаю и на основе вашего текущего решения , вот один эквивалент способ сделать это в jQuery.

Изменения Iсделано между // The jQuery way start. и // The jQuery way end..

ОБНОВЛЕНИЕ

Объединено с редактированием OP (добавлен новый HTML / тестовый пример), и я заменилкод JS с новым - _findEls(), который должен хорошо выполнять свою работу.

$(document).ready(function() {
  function findChildrenNearElementHead(element, selector, maxDistance = 15) {
    let curLen = 0,
      candidates = [];
    // The jQuery way start.
    function _findEls(el) {
      $(el).children().each(function() {
        if (curLen < maxDistance) {
          if ($(this).is(selector)) {
            candidates.push(this);
          } else if (this.firstElementChild /* it has children */ ) {
            /* only count text of element OR its children */
            return _findEls(this);
          }

          curLen += $(this).text().length;
        }
      });
    }

    _findEls(element);
    // The jQuery way end.

    return candidates;
  }

  function thereIsImportantContent(element) {
    return findChildrenNearElementHead(element, "h1,h2,strong").length > 0;
  }

  // specs code
  describe("findChildrenNearElementHead", function() {

    it("accept strong near head", function() {
      expect(thereIsImportantContent($("#test_case_1")[0])).toBeTruthy();
    });

    it("accept strong near head with children", function() {
      expect(thereIsImportantContent($("#test_case_2")[0])).toBeTruthy();
    });

    it("accept wrapped strong near head", function() {
      expect(thereIsImportantContent($("#test_case_3")[0])).toBeTruthy();
    });

    it("reject strong further down", function() {
      expect(thereIsImportantContent($("#test_case_4")[0])).toBeFalsy();
    });

    it("reject strong further down with copies near head", function() {
      expect(thereIsImportantContent($("#test_case_5")[0])).toBeFalsy();
    });
  });

  // load jasmine htmlReporter
  (function() {
    var env = jasmine.getEnv();
    env.addReporter(new jasmine.HtmlReporter());
    env.execute();
  }());
});
container {
  display: none;
}
<link href="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine.js"></script>
<script src="https://cdn.jsdelivr.net/jasmine/1.3.1/jasmine-html.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<container id="test_case_1">
  <!-- strong content at the beginning -> accept -->
  <p>Some <strong>content</strong></p>
  ...
  <p>other text</p>
  ...
  <p>Hey look: <strong>content</strong>!</p>
</container>

<container id="test_case_2">
  <!-- handle strong with children correctly -->
  <strong>Hey look: <span> content!</span></strong>
</container>

<container id="test_case_3">
  <p>Test</p>
  <p>Hey <strong> content!</strong></p>
</container>

<container id="test_case_4">
  <p>Something</p>
  ...
  <p>other text</p>
  ...
  <!-- strong content but located further down -> reject -->
  <p>Hey look: <strong>content</strong>!</p>
</container>

<container id="test_case_5">
  <!-- same text as in strong below triggering false accept -->
  <p>Some content</p>
  ...
  <p>other text</p>
  ...
  <!-- strong content but located further down -> should eject -->
  <p>Hey look: <strong>content</strong>!</p>
</container>
0 голосов
/ 31 мая 2018

Посмотрите этот другой пример, основываясь на вашем фрагменте и последних комментариях:

$(document).ready(function() {

  var checkAllContainers = true; //change to false to verify containers individually
  
  if (checkAllContainers) {
    // verifying by parent container
    // container3 will be at position 200+ 
    verifyStrongText("#main_container");
  } else {
    // verifying containers individually
    // this way your filter will check from begin, container3 will start at position 0
    verifyStrongText("#my_container_1");
    verifyStrongText("#my_container_3");
    verifyStrongText("#my_container_2");
  }
});

function verifyStrongText(idElement) {
  $(idElement).find("strong").filter(function() {
    var start = 0; // starting from begin
    var end = 88; // change this number if you want expand or restrict your search range
    var strong_text = $(this).text();
    //checks the index of texts between <strong> tag
    var index = $(idElement).text().substring(start, end).indexOf(strong_text);

    //check if index of text is inside estipuled range (not found = -1)
    var response = start <= index && index <= end;

    if (response) {
      //make your actions here when words compare and check response
      functionXYZ("> " + $(this).text() + "(" + index + ") >> " +
        response + " == true (expected)   >> " +
        (response == true ? "OK" : "WRONG"));
    } else {
      functionXYZ("> " + $(this).text() + "(" + index + ") >> " +
        response + " == false(expected) >> " +
        (response == false ? "OK" : "WRONG"));
    }

    return response; // will return an Object, not a boolean, doesn't reuse it

  }).css("background", "limegreen"); // optional visual effect to see eligible words
}

//You can call ANY javascript function from your code
function functionXYZ(msg) {
  console.log(msg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<container id="main_container">
  <container id="my_container_1">
    <p>Some <strong>content 1</strong></p>
    ...
    <p><strong>First Headline</strong></p>
    ...
    <p>Hey look: <strong>other content 2</strong>!</p>
  </container>

  <container id="my_container_2">
    <p>Something</p> content 3 ...
    <p><strong>Second Headline</strong></p>
    ...
    <p>Hey look: <strong>content 4</strong>!</p>
  </container>

  <container id="my_container_3">
    <p>Some content 5</p>
    ...
    <p><strong>Third Headline</strong></p>
    ...
    <p>Hey look: <strong>content 6</strong>!</p>
  </container>
</container>

Ваш код исправлен и работает с 2 примерами:

Если вы хотите использовать слайс, вам нужно поместить весь текст в селекторкак <p> чтобы нарезать его.

Функция слайса не действует для позиционера текста.

$(document).ready(
  //Here you're slicing between 4 and 10 <p> elements
  $("#my_container").find("p").slice(4, 10).find("strong").filter(
    function() {
      var index = $("#my_container").text().indexOf($(this).text());
      console.log("Testing: " + $(this).text(), " (Index: " + index + ")");
      return 0 <= index && index <= 10; //return is irrelevant and does nothing
    })
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<container id="my_container">
  <p>Some content</p>
  <p>Hey look: <strong>content</strong>!</p>
  <p>more content</p>
  ...
  <p><strong>Second Headline</strong></p>
  ...
  <span>even more content</span>
  <p>Hey look: <strong>other content</strong>!</p>
  <span>even more content</span>
</container>

Но вы можете сделать это по-другому, проверьте условие внутри функции фильтра:

$(document).ready(
  $("#my_container").find("strong").filter(
    function() {
      var start = 26;
      var limit = 69;
      var index = $("#my_container").text().indexOf($(this).text());
      console.log("Testing: " + $(this).text(), " (Index: " + index + ")");
      console.log(start <= index && index <= limit ? "OK!" : "OUT OF RANGE!");
      return start <= index && index <= limit;
    }).css('background','limegreen')
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<container id="my_container">
  Some content
  Hey look: <strong>content</strong>!
  <p>more content</p>
  ...
  <strong>Second Headline</strong>
  ...
  <span>even more content</span>
  <p>Hey look: <strong>other content</strong>!</p>
  <span>even more content</span>
</container>

Другой пример, основанный на ваших комментариях:

$(document).ready(
  $("#my_container").find("strong").filter(
    function() {
      var start = 2;
      // to find at the beginning of text use: var start = 0;
      var limit = 26;
      // to find until the end of text use: var limit = $("#my_container").text().length;

      var index = $("#my_container").text().substring(start, limit).indexOf($(this).text());
      console.log("Testing: " + $(this).text(), " (Index: " + index + ")");
      console.log(start <= index && index <= limit ? "OK!" : "OUT OF RANGE!");
      
      if(start <= index && index <= limit) {
         alert("What i need to do?");
      }
      
      return start <= index && index <= limit;

    }).css('background', 'limegreen')
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<container id="my_container">
  Some content Hey look: <strong>content</strong>!
  <p>more content</p>
  ...
  <strong>Second Headline</strong> ...
  <span>even more content</span>
  <p>Hey look: <strong>other content</strong>!</p>
  <span>even more content</span>
</container>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...