用什么工具绘制文件树图

给定一个文件树——一个包含目录的目录等等,你会如何编写一个脚本来创建一个文件树的图表作为一个图形文件,我可以嵌入到一个文字处理器文档中。 我更喜欢矢量(SVG,EPS,EMF...)文件。 该工具必须在 Windows 上运行,但最好是跨平台的。 该工具可能是商业性的,但最好是免费的。

更新2012-02-20。 这个问题与一个文档子项目有关。我必须解释文件(特别是资源和配置文件)所在的位置。 我最终使用了 dos tree 命令。我两个屏幕抓取结果(短文件夹)和较长的文件夹,我重定向到一个文本文件,然后我编辑。例如,如果一个子文件夹中包含20个类似类型的文件,而这些文件各自对于我正在制作的内容并不重要,那么我只留下两个文件,并用一行代替其余的文件。然后我再次打印文件到控制台,屏幕抓取它。 在屏幕抓取之前,我必须修改前景色为黑色,背景色为白色,以便看起来更好,并节省墨水在文件中应该被打印。

没有比这更好的工具了,这是非常令人惊讶的。如果我有时间,我会编写一个 Visio 扩展,或者可能是一些生成 SVG 的命令行。SVG 不符合 HTML5标准,甚至可以毫不费力地包含到在线文档中。

更新2017-10-17。 我很抱歉,这个问题被删除,因为不属于所以。所以我重新措辞了。我需要一个脚本-而不是所见即所得的工具。所以任何脚本语言或图书馆都可以。所以这是一个代码编写的问题,我相信属于 SO。

151920 次浏览

Why could you not just make a file structure on the Windows file system and populate it with your desired names, then use a screen grabber like HyperSnap (or the ubiquitous Alt-PrtScr) to capture a section of the Explorer window.

I did this when 'demoing' an internet application which would have collapsible sections, I just had to create files that looked like my desired entries.

HyperSnap gives JPGs at least (probably others but I've never bothered to investigate).

Or you could screen capture the icons +/- from Explorer and use them within MS Word Draw itself to do your picture, but I've never been able to get MS Word Draw to behave itself properly.

Graphviz - from the web page:

The Graphviz layout programs take descriptions of graphs in a simple text language, and make diagrams in several useful formats such as images and SVG for web pages, Postscript for inclusion in PDF or other documents; or display in an interactive graph browser. (Graphviz also supports GXL, an XML dialect.)

It's the simplest and most productive tool I've found to create a variety of boxes-and-lines diagrams. I have and use Visio and OmniGraffle, but there's always the temptation to make "just one more adjustment".

It's also quite easy to write code to produce the "dot file" format that Graphiz consumes, so automated diagram production is also nicely within reach.

Copying and pasting from the MS-DOS tree command might also work for you. Examples:

tree

C:\Foobar>tree
C:.
├───FooScripts
├───barconfig
├───Baz
│   ├───BadBaz
│   └───Drop
...

tree /F

C:\Foobar>tree
C:.
├───FooScripts
│    foo.sh
├───barconfig
│    bar.xml
├───Baz
│   ├───BadBaz
│   │    badbaz.xml
│   └───Drop
...

tree /A

C:\Foobar>tree /A
C:.
+---FooScripts
+---barconfig
+---Baz
¦   +---BadBaz
¦   \---Drop
...

tree /F /A

C:\Foobar>tree /A
C:.
+---FooScripts
¦    foo.sh
+---barconfig
¦    bar.xml
+---Baz
¦   +---BadBaz
¦   ¦    badbaz.xml
¦   \---Drop
...

Syntax [source]

tree [drive:][path] [/F] [/A]

drive:\path — Drive and directory containing disk for display of directory structure, without listing files.

/F — Include all files living in every directory.

/A — Replace graphic characters used for linking lines with ext characters , instead of graphic characters. /a is used with code pages that do not support graphic characters and to send output to printers that do not properly interpret graphic characters.

The advice to use Graphviz is good: you can generate the dot file and it will do the hard work of measuring strings, doing the layout, etc. Plus it can output the graphs in lot of formats, including vector ones.

I found a Perl program doing precisely that, in a mailing list, but I just can't find it back! I copied the sample dot file and studied it, since I don't know much of this declarative syntax and I wanted to learn a bit more.

Problem: with latest Graphviz, I have errors (or rather, warnings, as the final diagram is generated), both in the original graph and the one I wrote (by hand). Some searches shown this error was found in old versions and disappeared in more recent versions. Looks like it is back.

I still give the file, maybe it can be a starting point for somebody, or maybe it is enough for your needs (of course, you still have to generate it).

digraph tree
{
rankdir=LR;


DirTree [label="Directory Tree" shape=box]


a_Foo_txt [shape=point]
f_Foo_txt [label="Foo.txt", shape=none]
a_Foo_txt -> f_Foo_txt


a_Foo_Bar_html [shape=point]
f_Foo_Bar_html [label="Foo Bar.html", shape=none]
a_Foo_Bar_html -> f_Foo_Bar_html


a_Bar_png [shape=point]
f_Bar_png [label="Bar.png", shape=none]
a_Bar_png -> f_Bar_png


a_Some_Dir [shape=point]
d_Some_Dir [label="Some Dir", shape=ellipse]
a_Some_Dir -> d_Some_Dir


a_VBE_C_reg [shape=point]
f_VBE_C_reg [label="VBE_C.reg", shape=none]
a_VBE_C_reg -> f_VBE_C_reg


a_P_Folder [shape=point]
d_P_Folder [label="P Folder", shape=ellipse]
a_P_Folder -> d_P_Folder


a_Processing_20081117_7z [shape=point]
f_Processing_20081117_7z [label="Processing-20081117.7z", shape=none]
a_Processing_20081117_7z -> f_Processing_20081117_7z


a_UsefulBits_lua [shape=point]
f_UsefulBits_lua [label="UsefulBits.lua", shape=none]
a_UsefulBits_lua -> f_UsefulBits_lua


a_Graphviz [shape=point]
d_Graphviz [label="Graphviz", shape=ellipse]
a_Graphviz -> d_Graphviz


a_Tree_dot [shape=point]
f_Tree_dot [label="Tree.dot", shape=none]
a_Tree_dot -> f_Tree_dot


{
rank=same;
DirTree -> a_Foo_txt -> a_Foo_Bar_html -> a_Bar_png -> a_Some_Dir -> a_Graphviz [arrowhead=none]
}
{
rank=same;
d_Some_Dir -> a_VBE_C_reg -> a_P_Folder -> a_UsefulBits_lua [arrowhead=none]
}
{
rank=same;
d_P_Folder -> a_Processing_20081117_7z [arrowhead=none]
}
{
rank=same;
d_Graphviz -> a_Tree_dot [arrowhead=none]
}
}


> dot -Tpng Tree.dot -o Tree.png
Error: lost DirTree a_Foo_txt edge
Error: lost a_Foo_txt a_Foo_Bar_html edge
Error: lost a_Foo_Bar_html a_Bar_png edge
Error: lost a_Bar_png a_Some_Dir edge
Error: lost a_Some_Dir a_Graphviz edge
Error: lost d_Some_Dir a_VBE_C_reg edge
Error: lost a_VBE_C_reg a_P_Folder edge
Error: lost a_P_Folder a_UsefulBits_lua edge
Error: lost d_P_Folder a_Processing_20081117_7z edge
Error: lost d_Graphviz a_Tree_dot edge

I will try another direction, using Cairo, which is also able to export a number of formats. It is more work (computing positions/offsets) but the structure is simple, shouldn't be too hard.

As promised, here is my Cairo version. I scripted it with Lua, using lfs to walk the directories. I love these little challenges, as they allow me to explore APIs I wanted to dig for quite some time...
lfs and LuaCairo are both cross-platform, so it should work on other systems (tested on French WinXP Pro SP3).

I made a first version drawing file names as I walked the tree. Advantage: no memory overhead. Inconvenience: I have to specify the image size beforehand, so listings are likely to be cut off.

So I made this version, first walking the directory tree, storing it in a Lua table. Then, knowing the number of files, creating the canvas to fit (at least vertically) and drawing the names.
You can easily switch between PNG rendering and SVG one. Problem with the latter: Cairo generates it at low level, drawing the letters instead of using SVG's text capability. Well, at least, it guarantees accurate rending even on systems without the font. But the files are bigger... Not really a problem if you compress it after, to have a .svgz file.
Or it shouldn't be too hard to generate the SVG directly, I used Lua to generate SVG in the past.

-- LuaFileSystem <http://www.keplerproject.org/luafilesystem/>
require"lfs"
-- LuaCairo <http://www.dynaset.org/dogusanh/>
require"lcairo"
local CAIRO = cairo




local PI = math.pi
local TWO_PI = 2 * PI


--~ local dirToList = arg[1] or "C:/PrgCmdLine/Graphviz"
--~ local dirToList = arg[1] or "C:/PrgCmdLine/Tecgraf"
local dirToList = arg[1] or "C:/PrgCmdLine/tcc"
-- Ensure path ends with /
dirToList = string.gsub(dirToList, "([^/])$", "%1/")
print("Listing: " .. dirToList)
local fileNb = 0


--~ outputType = 'svg'
outputType = 'png'


-- dirToList must have a trailing slash
function ListDirectory(dirToList)
local dirListing = {}
for file in lfs.dir(dirToList) do
if file ~= ".." and file ~= "." then
local fileAttr = lfs.attributes(dirToList .. file)
if fileAttr.mode == "directory" then
dirListing[file] = ListDirectory(dirToList .. file .. '/')
else
dirListing[file] = ""
end
fileNb = fileNb + 1
end
end
return dirListing
end


--dofile[[../Lua/DumpObject.lua]] -- My own dump routine
local dirListing = ListDirectory(dirToList)
--~ print("\n" .. DumpObject(dirListing))
print("Found " .. fileNb .. " files")


--~ os.exit()


-- Constants to change to adjust aspect
local initialOffsetX = 20
local offsetY = 50
local offsetIncrementX = 20
local offsetIncrementY = 12
local iconOffset = 10


local width = 800 -- Still arbitrary
local titleHeight = width/50
local height = offsetIncrementY * (fileNb + 1) + titleHeight
local outfile = "CairoDirTree." .. outputType


local ctxSurface
if outputType == 'svg' then
ctxSurface = cairo.SvgSurface(outfile, width, height)
else
ctxSurface = cairo.ImageSurface(CAIRO.FORMAT_RGB24, width, height)
end
local ctx = cairo.Context(ctxSurface)


-- Display a file name
-- file is the file name to display
-- offsetX is the indentation
function DisplayFile(file, bIsDir, offsetX)
if bIsDir then
ctx:save()
ctx:select_font_face("Sans", CAIRO.FONT_SLANT_NORMAL, CAIRO.FONT_WEIGHT_BOLD)
ctx:set_source_rgb(0.5, 0.0, 0.7)
end


-- Display file name
ctx:move_to(offsetX, offsetY)
ctx:show_text(file)


if bIsDir then
ctx:new_sub_path() -- Position independent of latest move_to
-- Draw arc with absolute coordinates
ctx:arc(offsetX - iconOffset, offsetY - offsetIncrementY/3, offsetIncrementY/3, 0, TWO_PI)
-- Violet disk
ctx:set_source_rgb(0.7, 0.0, 0.7)
ctx:fill()
ctx:restore() -- Restore original settings
end


-- Increment line offset
offsetY = offsetY + offsetIncrementY
end


-- Erase background (white)
ctx:set_source_rgb(1.0, 1.0, 1.0)
ctx:paint()


--~ ctx:set_line_width(0.01)


-- Draw in dark blue
ctx:set_source_rgb(0.0, 0.0, 0.3)
ctx:select_font_face("Sans", CAIRO.FONT_SLANT_NORMAL, CAIRO.FONT_WEIGHT_BOLD)
ctx:set_font_size(titleHeight)
ctx:move_to(5, titleHeight)
-- Display title
ctx:show_text("Directory tree of " .. dirToList)


-- Select font for file names
ctx:select_font_face("Sans", CAIRO.FONT_SLANT_NORMAL, CAIRO.FONT_WEIGHT_NORMAL)
ctx:set_font_size(10)
offsetY = titleHeight * 2


-- Do the job
function DisplayDirectory(dirToList, offsetX)
for k, v in pairs(dirToList) do
--~ print(k, v)
if type(v) == "table" then
-- Sub-directory
DisplayFile(k, true, offsetX)
DisplayDirectory(v, offsetX + offsetIncrementX)
else
DisplayFile(k, false, offsetX)
end
end
end


DisplayDirectory(dirListing, initialOffsetX)


if outputType == 'svg' then
cairo.show_page(ctx)
else
--cairo.surface_write_to_png(ctxSurface, outfile)
ctxSurface:write_to_png(outfile)
end


ctx:destroy()
ctxSurface:destroy()


print("Found " .. fileNb .. " files")

Of course, you can change the styles. I didn't draw the connection lines, I didn't saw it as necessary. I might add them optionally later.