@ietf-tools/idnits
Version:
Library / CLI to inspect Internet-Draft documents for a variety of conditions to conform with IETF policies.
140 lines (134 loc) • 4.25 kB
JavaScript
import { isNil, isPlainObject } from 'lodash-es'
/**
* Matcher Function for findDescendantWith() and findAllDescendantsWith()
*
* @callback findChildWithClb
* @param {string} value Object value
* @param {string} key Object key
* @returns {Boolean} Return true on match
*/
/**
* Find a descendant that matches true for the given matcher function
*
* @param {Object} obj Object to traverse
* @param {findChildWithClb} func Function to call on every child for a match
* @param {string} [path] Current path array
* @returns {Object} Result with first descendant found
*/
export function findDescendantWith (obj, func, path = []) {
for (const [key, value] of Object.entries(obj)) {
if (func(value, key)) {
return {
path: [...path, key],
key,
value
}
}
if (Array.isArray(value)) {
let idx = 0
for (const entry of value) {
const result = findDescendantWith(entry, func, [...path, `${key}[${idx}]`])
if (result) {
return result
}
idx++
}
} else if (isPlainObject(value)) {
const result = findDescendantWith(value, func, [...path, key])
if (result) {
return result
}
}
}
}
/**
* Find all descendants that matches true for the given matcher function
*
* @param {Object} obj Object to traverse
* @param {findChildWithClb} func Function to call on every child for a match
* @param {string} [path] Current path array
* @returns {Object[]} Result with all descendants found
*/
export function findAllDescendantsWith (obj, func, path = []) {
const matches = []
if (isNil(obj)) { return [] }
for (const [key, value] of Object.entries(obj)) {
if (func(value, key)) {
matches.push({
path: [...path, key],
key,
value
})
}
if (Array.isArray(value)) {
let idx = 0
for (const entry of value) {
const result = findAllDescendantsWith(entry, func, [...path, `${key}[${idx}]`])
if (result?.length > 0) {
matches.push(...result)
}
idx++
}
} else if (isPlainObject(value)) {
const result = findAllDescendantsWith(value, func, [...path, key])
if (result?.length > 0) {
matches.push(...result)
}
}
}
return matches
}
/**
* Traverse an object with the given matcher function
*
* @param {Object} obj Object to traverse
* @param {findChildWithClb} func Function to call on every child
* @param {string} [path] Current path array
*/
export async function traverseAll (obj, func, path = []) {
const isAsyncFunc = func.constructor.name === 'AsyncFunction'
if (typeof obj === 'string') {
isAsyncFunc ? await func(obj, null, path) : func(obj, null, path)
} else {
for (const [key, value] of Object.entries(obj)) {
if (key === '_attr' || key === '#text') { continue }
isAsyncFunc ? await func(value, key, path) : func(value, key, path)
if (Array.isArray(value)) {
let idx = 0
for (const entry of value) {
await traverseAll(entry, func, [...path, `${key}[${idx}]`])
idx++
}
} else if (isPlainObject(value)) {
await traverseAll(value, func, [...path, key])
}
}
}
}
/**
* Traverse an object with the given matcher function for every values
*
* @param {Object} obj Object to traverse
* @param {findChildWithClb} func Function to call on every value
* @param {string} [path] Current path array
*/
export async function traverseAllValues (obj, func, path = []) {
const isAsyncFunc = func.constructor.name === 'AsyncFunction'
if (typeof obj === 'string') {
isAsyncFunc ? await func(obj, null, path) : func(obj, null, path)
} else {
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'string') {
isAsyncFunc ? await func(value, key, [...path, key]) : func(value, key, [...path, key])
} else if (Array.isArray(value)) {
let idx = 0
for (const entry of value) {
await traverseAllValues(entry, func, [...path, `${key}[${idx}]`])
idx++
}
} else if (isPlainObject(value)) {
await traverseAllValues(value, func, [...path, key])
}
}
}
}