Получить путь CSS из элемента Dom - PullRequest
17 голосов
/ 01 сентября 2010

Я получил эту функцию для получения cssPath:

var cssPath = function (el) {
  var path = [];

  while (
    (el.nodeName.toLowerCase() != 'html') && 
    (el = el.parentNode) &&
    path.unshift(el.nodeName.toLowerCase() + 
      (el.id ? '#' + el.id : '') + 
      (el.className ? '.' + el.className.replace(/\s+/g, ".") : ''))
  );
  return path.join(" > ");
}
console.log(cssPath(document.getElementsByTagName('a')[123]));

Но я получил что-то вроде этого:

html > body > div#div-id > div.site > div.clearfix > ul.choices > li

Но чтобы быть полностью правым, это должно выглядеть так:

html > body > div#div-id > div.site:nth-child(1) > div.clearfix > ul.choices > li:nth-child(5)

Кто-нибудь имел идею реализовать это просто в javascript?

Ответы [ 5 ]

20 голосов
/ 01 сентября 2012

Ответ, приведенный выше, фактически содержит ошибку - цикл while преждевременно разрывается, когда встречается с неэлементным узлом (например, текстовым узлом), что приводит к некорректному селектору CSS.

Вот улучшенная версия, которая исправляет эту проблему плюс:

  • Останавливается, когда встречается первый элемент предка с назначенным ему идентификатором
  • Использует nth-of-type(), чтобы сделать селекторы более читабельными
    var cssPath = function(el) {
        if (!(el instanceof Element)) 
            return;
        var path = [];
        while (el.nodeType === Node.ELEMENT_NODE) {
            var selector = el.nodeName.toLowerCase();
            if (el.id) {
                selector += '#' + el.id;
                path.unshift(selector);
                break;
            } else {
                var sib = el, nth = 1;
                while (sib = sib.previousElementSibling) {
                    if (sib.nodeName.toLowerCase() == selector)
                       nth++;
                }
                if (nth != 1)
                    selector += ":nth-of-type("+nth+")";
            }
            path.unshift(selector);
            el = el.parentNode;
        }
        return path.join(" > ");
     }
14 голосов
/ 01 сентября 2010

Чтобы всегда получить правильный элемент, вам нужно будет использовать :nth-child() или :nth-of-type() для селекторов, которые не уникально идентифицируют элемент. Итак, попробуйте это:

var cssPath = function(el) {
    if (!(el instanceof Element)) return;
    var path = [];
    while (el.nodeType === Node.ELEMENT_NODE) {
        var selector = el.nodeName.toLowerCase();
        if (el.id) {
            selector += '#' + el.id;
        } else {
            var sib = el, nth = 1;
            while (sib.nodeType === Node.ELEMENT_NODE && (sib = sib.previousSibling) && nth++);
            selector += ":nth-child("+nth+")";
        }
        path.unshift(selector);
        el = el.parentNode;
    }
    return path.join(" > ");
}

Вы можете добавить подпрограмму для проверки уникальных элементов в соответствующем контексте (например, TITLE, BASE, CAPTION и т. Д.).

5 голосов
/ 12 марта 2015

В двух других предоставленных ответах было несколько предположений о совместимости браузера, с которыми я столкнулся. Приведенный ниже код не будет использовать nth-child, а также имеет проверку предыдущего элемента ElementSibling.

function previousElementSibling (element) {
  if (element.previousElementSibling !== 'undefined') {
    return element.previousElementSibling;
  } else {
    // Loop through ignoring anything not an element
    while (element = element.previousSibling) {
      if (element.nodeType === 1) {
        return element;
      }
    }
  }
}
function getPath (element) {
  // False on non-elements
  if (!(element instanceof HTMLElement)) { return false; }
  var path = [];
  while (element.nodeType === Node.ELEMENT_NODE) {
    var selector = element.nodeName;
    if (element.id) { selector += ('#' + element.id); }
    else {
      // Walk backwards until there is no previous sibling
      var sibling = element;
      // Will hold nodeName to join for adjacent selection
      var siblingSelectors = [];
      while (sibling !== null && sibling.nodeType === Node.ELEMENT_NODE) {
        siblingSelectors.unshift(sibling.nodeName);
        sibling = previousElementSibling(sibling);
      }
      // :first-child does not apply to HTML
      if (siblingSelectors[0] !== 'HTML') {
        siblingSelectors[0] = siblingSelectors[0] + ':first-child';
      }
      selector = siblingSelectors.join(' + ');
    }
    path.unshift(selector);
    element = element.parentNode;
  }
  return path.join(' > ');
}
4 голосов
/ 01 июня 2017

Выполнение обратного поиска в селекторе CSS - сложная вещь.Обычно я сталкивался с двумя типами решений:

  1. Перейдите вверх по дереву DOM, чтобы собрать строку селектора из комбинации имен элементов, классов и id или name атрибут.Проблема этого метода заключается в том, что он может привести к тому, что селекторы возвращают несколько элементов, которые не обрежут его, если мы требуем, чтобы они выбрали только один уникальный элемент.

  2. Соберите строку селектораиспользуя nth-child() или nth-of-type(), что может привести к очень длинным селекторам.В большинстве случаев, чем длиннее селектор, тем выше специфичность, и чем выше специфичность, тем больше вероятность, что он сломается при изменении структуры DOM.

Приведенное ниже решение является попыткойрешая обе эти проблемы.Это гибридный подход, который выводит уникальный селектор CSS (т. Е. document.querySelectorAll(getUniqueSelector(el)) всегда должен возвращать массив из одного элемента).Хотя возвращаемая строка селектора не обязательно самая короткая, она выводится с учетом эффективности селектора CSS при одновременном выравнивании специфичности путем определения приоритетов nth-of-type() и nth-child() last.

Вы можете указать, какие атрибуты включить вселектор путем обновления массива aAttr.Минимальное требование к браузеру - IE 9.

function getUniqueSelector(elSrc) {
  if (!(elSrc instanceof Element)) return;
  var sSel,
    aAttr = ['name', 'value', 'title', 'placeholder', 'data-*'], // Common attributes
    aSel = [],
    // Derive selector from element
    getSelector = function(el) {
      // 1. Check ID first
      // NOTE: ID must be unique amongst all IDs in an HTML5 document.
      // https://www.w3.org/TR/html5/dom.html#the-id-attribute
      if (el.id) {
        aSel.unshift('#' + el.id);
        return true;
      }
      aSel.unshift(sSel = el.nodeName.toLowerCase());
      // 2. Try to select by classes
      if (el.className) {
        aSel[0] = sSel += '.' + el.className.trim().replace(/ +/g, '.');
        if (uniqueQuery()) return true;
      }
      // 3. Try to select by classes + attributes
      for (var i=0; i<aAttr.length; ++i) {
        if (aAttr[i]==='data-*') {
          // Build array of data attributes
          var aDataAttr = [].filter.call(el.attributes, function(attr) {
            return attr.name.indexOf('data-')===0;
          });
          for (var j=0; j<aDataAttr.length; ++j) {
            aSel[0] = sSel += '[' + aDataAttr[j].name + '="' + aDataAttr[j].value + '"]';
            if (uniqueQuery()) return true;
          }
        } else if (el[aAttr[i]]) {
          aSel[0] = sSel += '[' + aAttr[i] + '="' + el[aAttr[i]] + '"]';
          if (uniqueQuery()) return true;
        }
      }
      // 4. Try to select by nth-of-type() as a fallback for generic elements
      var elChild = el,
        sChild,
        n = 1;
      while (elChild = elChild.previousElementSibling) {
        if (elChild.nodeName===el.nodeName) ++n;
      }
      aSel[0] = sSel += ':nth-of-type(' + n + ')';
      if (uniqueQuery()) return true;
      // 5. Try to select by nth-child() as a last resort
      elChild = el;
      n = 1;
      while (elChild = elChild.previousElementSibling) ++n;
      aSel[0] = sSel = sSel.replace(/:nth-of-type\(\d+\)/, n>1 ? ':nth-child(' + n + ')' : ':first-child');
      if (uniqueQuery()) return true;
      return false;
    },
    // Test query to see if it returns one element
    uniqueQuery = function() {
      return document.querySelectorAll(aSel.join('>')||null).length===1;
    };
  // Walk up the DOM tree to compile a unique selector
  while (elSrc.parentNode) {
    if (getSelector(elSrc)) return aSel.join(' > ');
    elSrc = elSrc.parentNode;
  }
}
0 голосов
/ 17 ноября 2017

Я как-то нахожу все реализации нечитаемыми из-за ненужной мутации. Здесь я предоставляю мой в ClojureScript и JS:

(defn element? [x]
  (and (not (nil? x))
      (identical? (.-nodeType x) js/Node.ELEMENT_NODE)))

(defn nth-child [el]
  (loop [sib el nth 1]
    (if sib
      (recur (.-previousSibling sib) (inc nth))
      (dec nth))))

(defn element-path
  ([el] (element-path el []))
  ([el path]
  (if (element? el)
    (let [tag (.. el -nodeName (toLowerCase))
          id (and (not (string/blank? (.-id el))) (.-id el))]
      (if id
        (element-path nil (conj path (str "#" id)))
        (element-path
          (.-parentNode el)
          (conj path (str tag ":nth-child(" (nth-child el) ")")))))
    (string/join " > " (reverse path)))))

Javascript:

const isElement = (x) => x && x.nodeType === Node.ELEMENT_NODE;

const nthChild = (el, nth = 1) => {
  if (el) {
    return nthChild(el.previousSibling, nth + 1);
  } else {
    return nth - 1;
  }
};

const elementPath = (el, path = []) => {
  if (isElement(el)) {
    const tag = el.nodeName.toLowerCase(),
          id = (el.id.length != 0 && el.id);
    if (id) {
      return elementPath(
        null, path.concat([`#${id}`]));
    } else {
      return elementPath(
        el.parentNode,
        path.concat([`${tag}:nth-child(${nthChild(el)})`]));
    }
  } else {
    return path.reverse().join(" > ");
  }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...