Разбить массив путей к файлам на иерархический объект в JavaScript
Использование JSZip, который при распаковке файла дает мне список папок и файлов. Например, когда я бегу
files.forEach((relativePath, file) => {
console.log(relativePath);
});
Я получил:
three-dxf-master/
three-dxf-master/.DS_Store
three-dxf-master/.gitignore
three-dxf-master/LICENSE
three-dxf-master/README.md
three-dxf-master/bower.json
three-dxf-master/bower_components/
Некоторые из этих элементов являются каталогами, а некоторые - файлами. Я могу сказать, какие из них являются каталогами, проверив file.dir
, Я хотел бы разделить это на иерархическую структуру данных. Я хочу разделить это так:
{
"three-dxf-master": [
".DS_Store",
".gitignore",
"LICENSE",
"README.md",
"bower.json",
{
"bower_components": [
".DS_Store",
{
"dxf-parser": [...]
}
]
}
]
}
Таким образом, я могу отправить его в Vue и отформатировать в хорошем средстве просмотра файлов. Я просмотрел документы и не вижу простого способа создать иерархическую структуру данных для файлов. Я начал изучать это, взяв последний в пути к файлу после разделения.
3 ответа
Вот пример кода, который также обрабатывает файлы в корне.
См. Объяснение кода ниже фрагмента.
var paths = [
"three-dxf-master/",
"three-dxf-master/.DS_Store",
"three-dxf-master/.gitignore",
"three-dxf-master/LICENSE",
"three-dxf-master/README.md",
"three-dxf-master/bower.json",
"three-dxf-master/bower_components/",
"three-dxf-master/bower_components/.DS_Store",
"three-dxf-master/bower_components/dxf-parser/",
"three-dxf-master/bower_components/dxf-parser/foo",
"three-dxf-master/bower_components/dxf-parser/bar",
"three-dxf-master/dummy_folder/",
"three-dxf-master/dummy_folder/foo",
"three-dxf-master/dummy_folder/hello/",
"three-dxf-master/dummy_folder/hello/hello",
]
// Extract a filename from a path
function getFilename(path) {
return path.split("/").filter(function(value) {
return value && value.length;
}).reverse()[0];
}
// Find sub paths
function findSubPaths(path) {
// slashes need to be escaped when part of a regexp
var rePath = path.replace("/", "\\/");
var re = new RegExp("^" + rePath + "[^\\/]*\\/?$");
return paths.filter(function(i) {
return i !== path && re.test(i);
});
}
// Build tree recursively
function buildTree(path) {
path = path || "";
var nodeList = [];
findSubPaths(path).forEach(function(subPath) {
var nodeName = getFilename(subPath);
if (/\/$/.test(subPath)) {
var node = {};
node[nodeName] = buildTree(subPath);
nodeList.push(node);
} else {
nodeList.push(nodeName);
}
});
return nodeList;
}
// Build tree from root
var tree = buildTree();
// By default, tree is an array
// If it contains only one element which is an object,
// return this object instead to match OP request
if (tree.length == 1 && (typeof tree[0] === 'object')) {
tree = tree[0];
}
// Serialize tree for debug purposes
console.log(JSON.stringify(tree, null, 2));
объяснение
function getFilename(path) {
return path.split("/").filter(function(value) {
return value && value.length;
} ).reverse()
[0];
}
Чтобы получить имя файла, путь разделяется на
/
,/ путь / к / каталог / =>
['path', 'to', 'dir', '']
/ путь / к / файлу =>
['path', 'to', 'file']
Сохраняются только значения с длиной, этот дескриптор dir path.
Имя файла является последним значением нашего массива, чтобы получить его, мы просто обращаем массив и получаем первый элемент.
function findSubPaths(path) {
// slashes need to be escaped when part of a regexp
var rePath = path.replace("/", "\\/");
var re = new RegExp("^" + rePath + "[^\\/]*\\/?$");
return paths.filter(function(i) {
return i !== path && re.test(i);
});
}
Чтобы найти подпуть пути, мы используем фильтр по списку путей.
Фильтр использует регулярное выражение (демоверсия доступна здесь), чтобы проверить, начинается ли путь с родительского пути и заканчивается либо
/
(это путь к каталогу) или конец строки (это путь к файлу).Если проверенный путь не равен родительскому пути и не соответствует регулярному выражению, он принимается фильтром. В противном случае это отклонено.
function buildTree(path) {
path = path || "";
var nodeList = [];
findSubPaths(path).forEach(function(subPath) {
var nodeName = getFilename(subPath);
if(/\/$/.test(subPath)) {
var node = {};
node[nodeName] = buildTree(subPath);
nodeList.push(node);
}
else {
nodeList.push(nodeName);
}
});
return nodeList;
}
Теперь, когда у нас есть методы для извлечения имени файла из пути и для поиска подпутей, очень легко построить наше дерево. Дерево - это список узлов.
Если суб-путь заканчивается
/
тогда это реж, и мы называемbuildTree
рекурсивно перед добавлением узла в nodeList.В противном случае мы просто добавляем имя файла в nodeList.
Дополнительный код
if (tree.length == 1 && (typeof tree[0] === 'object')) {
tree = tree[0];
}
По умолчанию возвращаемое дерево является массивом.
Чтобы соответствовать OP-запросу, если он содержит только один элемент, который является объектом, мы возвращаем этот объект.
Информация
Искал реализацию после того, как попробовал все решения на этой странице, у каждого были ошибки.
Наконец я нашел это
Решение
Вам нужно будет добавить "/" к выводу путей jszip, чтобы использовать алгоритм, вы можете использовать цикл forEach.
var paths = [
'/FolderA/FolderB/FolderC/Item1',
'/FolderA/FolderB/Item1',
'/FolderB/FolderD/FolderE/Item1',
'/FolderB/FolderD/FolderE/Item2',
'/FolderA/FolderF/Item1',
'/ItemInRoot'
];
function arrangeIntoTree(paths, cb) {
var tree = [];
// This example uses the underscore.js library.
_.each(paths, function(path) {
var pathParts = path.split('/');
pathParts.shift(); // Remove first blank element from the parts array.
var currentLevel = tree; // initialize currentLevel to root
_.each(pathParts, function(part) {
// check to see if the path already exists.
var existingPath = _.findWhere(currentLevel, {
name: part
});
if (existingPath) {
// The path to this item was already in the tree, so don't add it again.
// Set the current level to this path's children
currentLevel = existingPath.children;
} else {
var newPart = {
name: part,
children: [],
}
currentLevel.push(newPart);
currentLevel = newPart.children;
}
});
});
cb(tree);
}
arrangeIntoTree(paths, function(tree) {
console.log('tree: ', tree);
});
Мне также нужно было отобразить данные в интерактивном дереве, я использовал https://github.com/wix/angular-tree-control, который принимает точный формат.
Вы можете разбить строки на записи, а затем разбить каждую запись на поля. При обработке определите, является ли поле каталогом или файлом. Если каталог, посмотрите, является ли он подкаталогом, и создайте его, если он не существует. Тогда двигайтесь в это.
Если это файл, просто нажмите в текущем каталоге.
Формат в OP не допускает наличие файлов в корневом каталоге, поэтому в следующем случае выдается сообщение об ошибке. Чтобы разрешить файлы в корне, базовый объект должен быть массивом (но, похоже, это объект).
Следующее также позволяет путям быть в любом порядке и создаваться не последовательно, например, он будет принимать:
foobar/fum
это не нужно:
foobar/
foobar/fum
Надеюсь, комментариев достаточно.
var data = 'three-dxf-master/' +
'\nfoobar/fumm' +
'\nthree-dxf-master/.DS_Store' +
'\nthree-dxf-master/.gitignore' +
'\nthree-dxf-master/LICENSE' +
'\nthree-dxf-master/README.md' +
'\nthree-dxf-master/bower.json' +
'\nthree-dxf-master/bower_components/' +
'\nthree-dxf-master/bower_components/.DS_Store' +
'\nthree-dxf-master/bower_components/dxf-parser/';
function parseData(data) {
var records = data.split(/\n/);
var result = records.reduce(function(acc, record) {
var fields = record.match(/[^\/]+\/?/g) || [];
var currentDir = acc;
fields.forEach(function (field, idx) {
// If field is a directory...
if (/\/$/.test(field)) {
// If first one and not an existing directory, add it
if (idx == 0) {
if (!(field in currentDir)) {
currentDir[field] = [];
}
// Move into subdirectory
currentDir = currentDir[field];
// If not first, see if it's a subdirectory of currentDir
} else {
// Look for field as a subdirectory of currentDir
var subDir = currentDir.filter(function(element){
return typeof element == 'object' && element[field];
})[0];
// If didn't find subDir, add it and set as currentDir
if (!subDir) {
var t = Object.create(null);
t[field] = [];
currentDir.push(t);
currentDir = t[field];
// If found, set as currentDir
} else {
currentDir = subDir[field];
}
}
// Otherwise it's a file. Make sure currentDir is a directory and not the root
} else {
if (Array.isArray(currentDir)) {
currentDir.push(field);
// Otherwise, must be at root where files aren't allowed
} else {
throw new Error('Files not allowed in root: ' + field);
}
}
});
return acc;
}, Object.create(null));
return result;
}
//console.log(JSON.stringify(parseData(data)));
console.log(parseData(data));