1. XenForo 1.5.14 中文版——支持中文搜索!现已发布!查看详情
  2. Xenforo 爱好者讨论群:215909318 XenForo专区

教你用当年老师说「不要用」的 Word 宏,完成超多工作

本帖由 漂亮的石头2016-03-09 发布。版面名称:知乎日报

  1. 漂亮的石头

    漂亮的石头 版主 管理成员

    注册:
    2012-02-10
    帖子:
    487,766
    赞:
    47
    什么是「宏」? 为什么以前计算机老师说不要用 Word 中的「宏」?

    [​IMG] Roc Lee,李弱可。

    -

    「看到这题目就觉得跟我无关。」我猜大部人都是这么想的。

    在曾经的很长一段时间内,Office 软件里的「宏」对我来说,也像是房里墙上的电视信号接口——住进这房子的时候,它就在那儿了,但是,我又似乎永远都用不到。不过搁在那儿我也不嫌弃,毕竟,万一哪天我心血来潮,想开始过一种看电视的生活呢?

    日子稀松平常地过着,直到有一天,我在网上无意读到一个人这么提及「Word 宏」——

    Word 里的「宏」大致相当于 Photoshop 里的「录制动作」吧。

    一句话打开新世界。

    这大概就像有人忽然对我说:「体育台有直播世界杯,高清,不缓冲,无延时!」

    这时我大概就有冲动关掉「射门!七,七,七球,球,球射进进进了,了,了,了,了,守偶偶偶偶门,员,的,的,的,怀里!」的网络直播,赶紧买台电视来看看。

    稍稍给自己一点耐心,看完这篇文字,你会发现,原来「宏」可以用这么简单的方法,完成这么复杂的工作。

    -

    别误会,我也是小白。有时候,小白给小白讲入门知识好像说得更清楚。

    -

    不知道「宏」是什么的人,可能最关心「宏」有什么用。

    直接引用宝典《Word 排版艺术》好了——

    宏可以做些什么事?
    超乎一般想象!本书举了三个实例:
    (1)两岸术语转换
    (2)为程序代码加行号
    (3)列出文档所用字体,详见 10.4 节~10.6 节。
    宏是一般用户和超级用户(Power Users)的分水岭。
    宏还可以用来写病毒(练功可以,千万别害人害己)。​

    (引文里提到的实例,大家自己买书看教程哈。我下面用的是自己的案例。)

    所以,我想这里已经可以回答题主的疑惑了。计算机老师为什么说不要用「宏」?因为

    (1)绝大部分 Office 用户无意成为「超级用户」,「宏」的使用也不在教学范围内,这使得第二点的意义显得更加重大——

    (2)「宏」的程序属性使之可能变成计算机病毒。而没有学过「宏」的用户无法分辨一个来源不可靠的「宏」是不是病毒,


    所以,索性一刀切,不要碰「宏」,就不会有安全风险了。

    我怀疑,写到这里,我已经失去了百分之八十三点五的读者。因为大家看到「超级用户」四个字,会以为「宏」是一种深不可测的东西。

    留下来的百分之十六读者,恭喜你们!「宏」的初级用法其实是零基础就可以轻松学会的!

    (还有百分之零点五,我知道你们会用「宏」,别看了别看了,快干正事去,我这是自己还才往门槛里瞥了一眼就在唾沫横飞地给门外的群众讲解里面的世界多精彩了,对你们肯定没有帮助)

    -

    因为接下来全是用实例讲「宏」,所以先列个实例目录好了:

    【实例 1】一键将文中所有弯角引号替换为直角引号

    【实例 2】写剧本时一键插入新的场景信息模板

    【实例 3】剧本自动整理成场景信息表格

    【实例 4】独立的两份中英文稿做成一份原文与译文每段对应的文稿​

    -

    -

    为了解释「宏」的作用,我先举一个在知乎最接地气的例子。

    【实例 1】
    把文中所有弯角引号替换为直角引号


    我之前公司的剧本,和知乎的潮流一样,是用直角引号的。但有的编剧同事,比如 @殷鉴老师,惯用的输入法没法方便地打出直角引号来,所以,只好在每次写完一集剧本后,「全部替换」一遍左引号,再「全部替换」一遍右引号。如果文中还有单引号,那就再分别「全部替换」左、右两种单引号。​

    使用宏的话,这个问题可以「一键解决」。只要在「宏」选项上选择「录制宏」,然后按照往日的顺序,操作那四次「全部替换」,然后在「宏」选项上选择「停止录制」,一个新的宏就诞生了。

    [​IMG]

    如果开始录制的时候选择了「将宏指定到按钮」,则根据你的设置,Word 的面板上会有一个该宏的按钮。

    [​IMG]

    如果选择的是「将宏指定到键盘」,则根据你的设置,该宏有一个特定的快捷键。

    当你再想把一篇文字中的所有弯角引号替换为直角引号时,再也不需要进行那些繁琐的操作了,只要按一下你给这个宏设定的快捷键,所有替换工作就会自动完成。

    它的本质就是把你的操作记录了下来,然后进行「重放」。

    就像无色禅师送给郭襄的那对铁罗汉——那算得上一个记录了少林罗汉拳的「宏」。按一下快捷键,它们就把罗汉拳从头到尾打一遍。

    真是简单得不能再简单了。

    当然,你也可以不指定快捷方式,而是通过「查看宏」到宏列表中选择想要编辑或使用的宏。

    在编辑界面中你会看到这个宏的代码,大致就是这样——


    Sub 弯角引号替换为直角引号() ' ' 弯角引号替换为直角引号 宏 ' ' Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = ChrW(8220) .Replacement.Text = "「" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "“" .Replacement.Text = "「" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll With Selection.Find .Text = "”" .Replacement.Text = "」" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll With Selection.Find .Text = "‘" .Replacement.Text = "『" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll With Selection.Find .Text = "’" .Replacement.Text = "』" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll End Sub

    事实上,如果你懒得录制宏,直接把这段代码复制粘贴到宏的编辑器里都行。

    -

    然后来个进阶的。

    【实例 2】
    写剧本的时候每次开始一个新的场景时都要重新写一个场景信息表,要么就得到上面把上一个场景的场景信息复制下来粘贴好再改。有没有办法不用这么麻烦吗?

    这个问题问得好!

    好莱坞对剧本格式有特别严格的要求,字体字号各种参数都有通用的规范。国内没有统一的规格,只有几种主流。比如我之前公司的剧本格式是这样的——

    [​IMG]

    每次重新插一个表格或者去复制上面的表格总是太麻烦。所以我们照样可以用「录制宏」来把插入表格的动作记下来。

    我给公司做的这个宏,代码如下:


    Sub 开始一个新的场景() ' ' 开始一个新的场景 宏 ' ' Selection.Font.Name = "仿宋" Selection.Font.Size = 12 With Selection.ParagraphFormat .LeftIndent = CentimetersToPoints(0) .RightIndent = CentimetersToPoints(0) .SpaceBefore = 0 .SpaceBeforeAuto = False .SpaceAfter = 0 .SpaceAfterAuto = False .LineSpacingRule = wdLineSpaceSingle .Alignment = wdAlignParagraphJustify .WidowControl = False .KeepWithNext = False .KeepTogether = False .PageBreakBefore = False .NoLineNumber = False .Hyphenation = True .FirstLineIndent = CentimetersToPoints(0) .OutlineLevel = wdOutlineLevelBodyText .CharacterUnitLeftIndent = 0 .CharacterUnitRightIndent = 0 .CharacterUnitFirstLineIndent = 0 .LineUnitBefore = 0 .LineUnitAfter = 0 .MirrorIndents = False .TextboxTightWrap = wdTightNone .CollapsedByDefault = False .ReadingOrder = wdReadingOrderLtr .AutoAdjustRightIndent = True .DisableLineHeightGrid = False .FarEastLineBreakControl = True .WordWrap = True .HangingPunctuation = True .HalfWidthPunctuationOnTopOfLine = False .AddSpaceBetweenFarEastAndAlpha = True .AddSpaceBetweenFarEastAndDigit = True .BaseLineAlignment = wdBaselineAlignAuto End With ActiveDocument.Tables.Add Range:=Selection.Range, NumRows:=1, NumColumns:= _ 3, DefaultTableBehavior:=wdWord9TableBehavior, AutoFitBehavior:= _ wdAutoFitFixed With Selection.Tables(1) If .Style <> "网格型" Then .Style = "网格型" End If .ApplyStyleHeadingRows = True .ApplyStyleLastRow = False .ApplyStyleFirstColumn = True .ApplyStyleLastColumn = False .ApplyStyleRowBands = True .ApplyStyleColumnBands = False End With Selection.TypeText Text:="场" Selection.Range.Cells(1).PreferredWidthType = wdPreferredWidthPoints Selection.Range.Cells(1).PreferredWidth = CentimetersToPoints(3.5) Selection.MoveRight Unit:=wdCell Selection.Range.Cells(1).PreferredWidthType = wdPreferredWidthPoints Selection.Range.Cells(1).PreferredWidth = CentimetersToPoints(8.5) Selection.MoveRight Unit:=wdCell Selection.TypeText Text:="日夜 / 内外" Selection.Range.Cells(1).PreferredWidthType = wdPreferredWidthPoints Selection.Range.Cells(1).PreferredWidth = CentimetersToPoints(5.5) Selection.MoveRight Unit:=wdCharacter, Count:=2 Selection.TypeParagraph Selection.TypeText Text:="△" End Sub

    这个宏的效果就是,按一下该宏的快捷键,Word 文档里就会自动插入下图红框内的内容:

    [​IMG]

    二十集剧本写下来,还是能省不少时间的。

    关于录制这个宏,要注意的是,「宏」记录的实质上是键盘动作,所以,部分鼠标动作在录制宏的时候不能使用,比如,不能用鼠标拖动来更改表格的宽度。所以在记录这个「宏」的时候,我需要把光标移到相应单元格内,通过右键设定「表格属性」来指定相应单元格的宽度。

    还是引用一下《Word 排版艺术》里的解释吧,说得比我清楚:

    录制过程中,可使用鼠标点击:
    ✓ 菜单上的命令
    ✓ 对话框中的选项

    但录制器并不录制:
    ╳ 文档视窗中的鼠标轨迹​

    因此录制过程中您不能借着「点击」或「拖曳」等鼠标动作来移动文档插入点,或以鼠标来选取、复制、搬移任何东西。这些行为都必须改以键盘来进行,才会被录制下来。

    -

    这个回答写到一半,我发现我真是没什么生活……

    -

    接下来说个进阶的。

    创建「宏」的另一种办法。

    好吧这次是真的进阶了。

    因为真的要使用程序语言了。

    只是,程序语言可能没有大部分朋友想像的那么复杂。比如我,我是没上过编程课的,所以很多在大学时上过 VB 课的朋友的编程基础都会比我好——我甚至是在写了几个宏之后才知道我用的这个语言叫 VBA(算是 VB 的一种)。

    在写这几个宏以前,我与程序语言的唯一亲密接触是——

    因为从来没听过 VF 课所以铁定过不了计算机二级的我乖乖揣着报名费准备去报个计算机一级轻松一过以便拿到学位证,正排着队忽然听说还有「计算机三级网络技术」这样一种听上去逼格不错的东西,怀着因长久以来捣腾路由器交换机什么的之类的超然自信,报名的一刻我在「计算机一级」的「一」字上加了两横……

    这就是我怎样遇上 C 语言的故事。因为没钱参加培训班,加上闻名遐迩的拖延症,零基础的我在 C 语言机试倒数只剩七天的时候才开始捧起《C 语言程序设计》——没错就是谭浩强老师那本,大概正好证明了我无人指导的状态……因为群众们对这本书的吐槽我是很多年后才在知乎上看到的……我一直以为这书挺好的,毕竟编程零基础的我就啃了一个星期,就把计算机三级的 C 语言考了 100 分(这么描述的话那些没参加过这项考试的人就不知道机试的成绩其实只有 0 分和 100 分两种情况了……)。

    别误会,我并不懂 C 语言。虽然如果我在高中时代就能知道编程是一件这么好玩的事情很可能就会选择读理科了,但这「七天过三级」的经历,确实是单纯为了考试。

    因为是单纯为了考试,所以我没有学习第九章以后的内容。学过的朋友都知道,那是虽然机试多半考不到但是却无比重要的内容——指针。

    毕竟,

    学习指针是学习 C 语言中最重要的一环,能否正确理解和使用指针是我们是否掌握 C 语言的一个标志。
    ——谭浩强《C 语言程序设计》第 10 章 指针

    上面唠叨了这么一大段,并不只是一个文科生借一个罕见的机会吹嘘自己解锁的理科成就,其实更是为了给下文做铺垫。因为我这尴尬的半吊子的水平,必定会两边不讨好——

    没学过的编程的朋友不能理解我为什么会产生某些思路,
    学过编程的朋友不能理解我的想法为什么如此幼稚……


    进阶实例。

    【实例 3】
    用剧本自动整理出场景表信息


    场景表信息整理是影视行业一件等因奉此的繁琐工作,其中被我称为工作性质像「在火柴厂粘纸盒」的一步毫无技术含量的工作就是将剧本中的场景信息复制到表格里。

    也就是要拿着这样的剧本:
    [​IMG]
    把里面的场景信息提取出来,做出这样的成品:
    [​IMG]

    显然,一条一条去复制粘贴整理真的是一件十分枯燥无趣的事情。

    繁,故而「宏」之。

    这显然不能用之前「录制动作」的简单办法来完成,因为每个剧本的场景数量是不同的,所以这个剧本的整理「动作」并不能套用到那个剧本里。

    所以才要编程。

    -

    初级阶段的程序语言使用其实跟初级阶段的自然语言使用没什么两样,都是通过粗笨的「仿写句子」实现的,具体来说,就是学会词汇,然后学会模仿句式,把词汇以例句的相应格式串起来。

    如果我是一个美国人,学会「蝙蝠侠可能打不过超人」这样一个例句以后,想用中文表达「a teenager can't beat a professionally trained MMA fighter」,就要学会「teenager」「professionally」「trained」「MMA」「fighter」等词在中文中的对应词汇。

    当我发现中文中没有和「teenager」完全对应的词汇时,我就需要按照我的表达需求,去用更低级的词汇,来组成「十几岁的孩子」或者「青少年」或者「十三岁到十九岁的人」等表达。

    然后我就能说出「十几岁的孩子打不过专业训练过的综合格斗战士」。

    当然,也有可能我是一个六维世界的生物,我发现我在六维层面的观察用你们这个世界的语言根本无法表达,所以只好作罢。

    程序语言的初级运用原理一模一样。只是我们学习自然语言的时候是对别人表达,学习程序语言的时候是对计算机表达。

    -

    现在我想向计算机表达我的需求。

    我发现对计算机说「给我把这些信息整理成表格」是没用的,因为就像中文里没有「teenager」或者英文里没有「姑父」一样,我得用计算机能理解的低级词汇把这个意思表达出来。

    先把 Word 能理解的意思列出来:


    (说来好笑,用必应搜「Word 快捷键」,我翻到第二十页实在翻不动了,也搜不到这个官方的页面……只能用「Word 快捷键 Office」做关键字才能搜到……)

    然后看看这些意思怎么用「宏」所使用的 VBA 语言表达出来。很简单,只要用上面提到的「录制宏」把想表达的「动作」录一遍,就能在「查看宏」里看到录下的「动作」是怎么用 VBA 语言表达的了。

    比如上面的「引号替换」录下的宏里就能很轻易地找到「全部替换」这个「动作」的表达:


    Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "“" .Replacement.Text = "「" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With

    这一段代码只要把被替换的弯角左引号「“」和替换为的弯角左引号「「」替换成别的「被替换」「替换为」内容,就能完成其他任何「全部替换」任务了。

    同样通过查看所录制的宏,还可以学到其他各种表达,比如「光标移至下一个单元格」是这样的:


    Selection.MoveRight Unit:=wdCell

    「按 1 下左键」的表达是这样的:


    Selection.MoveLeft Unit:=wdCharacter, Count:=1

    如是等等。

    那么剧本里的场景信息,怎么整理呢?

    首先,我得把剧本内容删掉,只剩下场景信息(所以千万别在剧本原稿上操作!)。

    这就涉及到定位的问题了。

    我问计算机:你知道哪些是剧本内容吗?

    计算机:不知道。

    我:啊啊啊啊啊啊啊啊啊。

    我只好慢慢研究文档中剧本内容和场景信息分别有哪些特点:

    3.1.1、场景信息在表格里;剧本内容不在。

    / 很好,感觉是用得上的特点。

    3.1.2、场景信息的表格都只占一行。

    / 很好,感觉在循环操作的时候这一点很有用。

    3.1.3、每个场景的剧本内容的行数、段数都是不确定的。

    / 很不好!循环操作的时候没法告诉计算机要删多少行或多少段!

    所以我的第一步,是要处理「剧本内容行数、段数不确定」这个问题。

    所以这个宏的第一步,就是一个「全部替换」,把文中所有的换行符「^p」,全部删掉。


    Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "^p" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll

    于是文档变成了这样:

    [​IMG]

    这个诡计利用了「删除换行符不会对本文档中表格内内容产生影响」这一特点,把每个场景的剧本内容变成了只有一段。

    接下来就容易了,我告诉计算机接下来要做的事情是:

    3.2.1、光标移至文首。

    3.2.2、光标往下移一行(到了该场景剧本内容的段首);

    3.2.3、选中这段剧本内容(研究官网的 Word 快捷键列表说明和实践尝试后可以知道,按 F8 键四次即可选中该段落);

    3.2.4、将选中的段落删除(拜拜剧本内容);

    3.2.5、再按一下删除键,使光标到达下个场景的场景信息表格行首,且下个场景的场景信息表格与上一场的信息表格合为一个表格。也就是——

    从这样

    [​IMG]

    变成了这样

    [​IMG]

    因为此时光标已位于下一场景的行首(具条到上图,就是位于「场 2」的「场」字前面),所以只要告诉计算机重复上述五步中的 3.2.2 到 3.2.5 这四步就行了。

    循环操作,这个是计算机最擅长的。

    因为学过几句 C 语言,所以我赶紧去查「循环」的表达方式。果然也有好几种表达。不过作为一个「小词主义者」,在能满足目前需求的情况下,我总是只记最先看到的那种表达:


    Do 我想让计算机循环做的事 Loop

    [​IMG]

    就这么简单。

    事实上,这一步是唯一真正需要用到程序语言的地方。因为「我想让计算机循环做的事」,你并不需要学会怎么表达,只要用「录制宏」操作一遍,然后在「查看宏」看看怎么表达,然后把这一段动作在宏代码中对应的代码用上面「Do」和「Loop」围起来就行。

    计算机就能不断循环为你重复这些动作了。

    结果计算机问了:「那你打算让我什么时候停下来?」

    呃……

    很多刚用代码写宏的同学都遇到了我这样的麻烦,写完一运行,Word 就完全停不下来(当然有的复杂宏确实要运行很久),跟死机了一样……

    因为我们忘了告诉计算机什么时候停下来。所以计算机在做完本份的工作后,还在马不停蹄地重复着徒劳的工作,顺便还告诉你一声「程序无法响应」。

    现在你已经开始大概稍稍能理解为什么「宏」可以变成病毒了。当然,这只是冰山一角。

    在现在这个案例里,怎么让计算机知道什么时候刚停止循环呢?

    我脑海里冒出来的第一个天真想法是这样的:

    按照上面的步骤进行循环的话,最后一个场景处理完,光标就正好来到文档的末尾。
    所以我只要告诉计算机「光标到了文档尾部就停止循环」就行了。​

    结果计算机冷冷地对我说:

    我不知道什么叫「光标到了文档尾部」。​

    于是我抓狂了。

    好多年没碰过程序语言的我差点放弃,才想起来,「光标到了文档尾部」可以用更低级的语言来解释:

    3.3.1、选择从光标位置到文档末尾的所有内容;

    3.3.2、让计算机看看这段内容里有多少个字节;

    3.3.3、如果这段内容的字节数多于 2(我也不知道为什么是多于 2 而不是多于 1 或者多于 0,反正是通过实验与教训学到的,我猜有可能是因为文尾的换行符也占两个字节吧,因为换行符在 Word 中等价于「^p」),就说明还没到文档尾部!

    哈!

    所以这个循环最后写成了这样:


    Selection.HomeKey Unit:=wdStory Selection.MoveDown Unit:=wdLine, Count:=1 Selection.EndKey Unit:=wdStory, Extend:=wdExtend '光标移到文首后,下移一行,选择光标所在处到文尾的所有内容,以判断是否还有剩余内容 Do While Len(Selection) > 2 Selection.MoveLeft Unit:=wdCharacter, Count:=1 '判断条件为选择内容长度是否大于 2 个字节,如果超过,说明还有剩余内容 Selection.Extend Selection.Extend Selection.Extend Selection.Extend '选择一个段落(删除所有换行符后,这里选择的一个段落即夹在两行表格之间的文字) Selection.Delete Unit:=wdCharacter, Count:=1 '删除这个段落(只留表格) Selection.Delete Unit:=wdCharacter, Count:=1 '再操作一次删除,使下一行表格与上一行表格合并为同一表格 Selection.MoveDown Unit:=wdLine, Count:=1 Selection.EndKey Unit:=wdStory, Extend:=wdExtend '回到循环操作状态 Loop

    所以我用的这个循环表达式的完整格式:


    Do While 需要满足的条件 我想要计算机循环做的事 Loop

    但是这个循环做完以后,我的场景表信息还没整理好,因为,剧本中的场景信息里,「日夜」和「内外」信息在同一个表格里。而我需要的完成品里,「日夜」信息和「内外」信息应该在相邻的两个单元格里。而且我不需要「日夜 / 内外」中间那个「/」符号,还有里面的空格,我也不需要。

    这个操作倒很简单:

    3.4.1、光标移至表格最右,然后在右边加入一列新表格

    3.4.2、将「日夜 / 内外」信息中的「内」或「外」剪切粘贴到右边新插入的这列表格中

    3.4.3、删除这两列表格中的「/」符号和空格符号

    最后就是把场景序号里的「场」字删掉,并把场序的「1」到「9」变成「01」到「09」这样的两位数字格式。

    这样就算是完成了。但你会发现刚刚操作「日夜 / 内外」的时候在右边新加的那列表格几乎伸到页面外边去了,所以,你可以再把表格的宽度也调整一下。

    最后的成品代码,就是这样的——


    Sub 剧本转场景表() ' ' 剧本转场景表 宏 ' ' Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "^p" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll ' 将所有换行符删除 Selection.HomeKey Unit:=wdStory Selection.MoveDown Unit:=wdLine, Count:=1 Selection.EndKey Unit:=wdStory, Extend:=wdExtend '光标移到文首后,下移一行,选择光标所在处到文尾的所有内容,以判断是否还有剩余内容 Do While Len(Selection) > 2 Selection.MoveLeft Unit:=wdCharacter, Count:=1 '判断条件为选择内容长度是否大于 2 个字节,如果超过,说明还有剩余内容 Selection.Extend Selection.Extend Selection.Extend Selection.Extend '选择一个段落(删除所有换行符后,这里选择的一个段落即夹在两行表格之间的文字) Selection.Delete Unit:=wdCharacter, Count:=1 '删除这个段落(只留表格) Selection.Delete Unit:=wdCharacter, Count:=1 '再操作一次删除,使下一行表格与上一行表格合并为同一表格 Selection.MoveDown Unit:=wdLine, Count:=1 Selection.EndKey Unit:=wdStory, Extend:=wdExtend '回到循环操作状态 Loop Selection.MoveRight Unit:=wdCharacter, Count:=1 Selection.HomeKey Unit:=wdStory Selection.MoveDown Unit:=wdLine, Count:=1 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.InsertColumnsRight '光标移至表格最右,然后在右边加入一列新表格 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.MoveLeft Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.MoveRight Unit:=wdCharacter, Count:=1 Selection.Extend Selection.Extend Selection.Extend Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend Selection.Cut Selection.MoveRight Unit:=wdCharacter, Count:=1 Selection.PasteAndFormat (wdFormatOriginalFormatting) '将「日夜 / 内外」信息中的「内」或「外」剪切粘贴到右边新插入的这列表格中 Selection.HomeKey Unit:=wdLine Selection.Delete Unit:=wdCharacter, Count:=1 Selection.EndKey Unit:=wdLine Selection.MoveRight Unit:=wdCharacter, Count:=2 Selection.EndKey Unit:=wdStory, Extend:=wdExtend '从下一行首选择到文尾,作为判断操作是否结束的条件 Do While Len(Selection) > 1 '循环操作【将「日夜 / 内外」信息中的「内」或「外」剪切粘贴到右边新插入的这列表格中】 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.MoveDown Unit:=wdLine, Count:=1 Selection.MoveLeft Unit:=wdCharacter, Count:=2 Selection.MoveLeft Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.MoveRight Unit:=wdCharacter, Count:=1 Selection.Extend Selection.Extend Selection.Extend Selection.MoveRight Unit:=wdCharacter, Count:=1, Extend:=wdExtend Selection.MoveLeft Unit:=wdCharacter, Count:=1, Extend:=wdExtend Selection.Cut Selection.MoveRight Unit:=wdCharacter, Count:=1 Selection.PasteAndFormat (wdFormatOriginalFormatting) Selection.HomeKey Unit:=wdLine Selection.Delete Unit:=wdCharacter, Count:=1 Selection.EndKey Unit:=wdLine Selection.MoveRight Unit:=wdCharacter, Count:=2 Selection.EndKey Unit:=wdStory, Extend:=wdExtend Loop Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = " " .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll '删除掉所有空格 Selection.HomeKey Unit:=wdStory Selection.EndKey Unit:=wdColumn, Extend:=True Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "场" .Replacement.Text = "" .Forward = True .Wrap = wdFindAsk .Format = False .MatchCase = False .MatchWholeWord = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll '删除第一列中的「场」字,只留下序号 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCell Do While Len(Selection) = 1 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.TypeText Text:="0" Selection.MoveDown Unit:=wdLine, Count:=1 Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCell Loop '用循环将序号 1 至 9 改写成 01、02、03……的形式 Selection.MoveRight Unit:=wdCharacter, Count:=1 Selection.Tables(1).PreferredWidthType = wdPreferredWidthPoints Selection.Tables(1).PreferredWidth = CentimetersToPoints(16) '调整表格宽度 End End Sub

    -

    再来一个实例。

    【实例 4】
    独立的两份中英文稿做成一份原文与译文每段对应的文稿

    具体是这样的。
    当我在之前之前的公司做电视剧字幕翻译的时候,公司使用的翻译软件是非常不错的生产力工具,能大幅提高工作的效率。操作前后,文稿的变化是这样的——

    原稿:
    [​IMG]
    译稿:
    [​IMG]
    (不要为《新还珠格格》感到震惊啦,《爱情公寓》我都翻过的!
    怎么把相声或者中式笑话翻译用英语翻译出来并且让老外觉得好笑? - Roc Lee 的回答

    现在问题出现了,因为我们的译稿需要交给后期的同学去上字幕,而且字幕组(好吧这不是大家理解的那个字幕组)需要的是中英文对照的文稿,也就是这样的:

    [​IMG]
    除非我们直接在 Word 里翻译(那样就放弃了提高翻译效率的工具),否则就得在翻译完之后想办法拿原稿和译稿整理出这样一份中英对照稿。​

    其实因为原稿和译稿的格式都完全一致(行数、行序都一致),所以要这样梅花间竹地排起来,其实也不困难。比如放到 Excel 的相邻两列里,复制出来粘贴到记事本里(这样就去掉了表格),再粘到 Word 里,然后把相应的空格(因为复制的是表格,所以表格信息会在记事本里变成了 Table 空格)替换成换行符,就成了。

    但上面这种方法的问题在于,译文中原有的格式信息(比如斜体、不同字体颜色等)就在复制粘贴的过程中消失了。

    所以还是得在 Word 里解决。

    理论上来说,用「宏」解决这个问题很简单。

    4.1.1、光标移到原文第一段段尾,另起一行;

    4.1.2、切换到译文,选择第一段,剪切;

    4.1.3、切换到原文,粘贴;

    4.1.4、光标移到下一段段屋,另起一行……

    只要不断重复 4.1.2 到 4.1.4 这三步就行了。

    确实很简单。

    不幸的是,事实上,不管是你在「Word 快捷键列表」里查到的

    Ctrl+F6 | 有多个窗口打开时,切换到下一个窗口。
    Ctrl+Shift+F6 |切换到上一个窗口​

    还是你常用的「Alt+Table」切换窗口,

    在 Word「宏」里都是无效的。

    (据说在上古时期是有效的,但因为「切换窗口」隐藏的可怕破坏力量,微软禁用了这几个快捷键在「宏」中的功能)

    所以你没法让计算机自己在原文和译文两个文稿之间不断切来切去。

    所以得另起炉灶。

    因为不能同时操作多个文稿,所以得自己先做点准备工作。

    在 Word 里新建一个上下两格的表格,把原文全文剪切到上面的单元格里,把译文全文剪切到下面的单元格里。

    [​IMG]

    准备好要处理的文稿后,我还要面对这样一个问题——

    我要选择一段文字时,使用的固定方法是按四下「F8」键。

    但是,「F8」键的作用是这样的,按一下,打开扩展选择模式,再按几下就再扩展几下,直到选中全文。

    所以「F8」键按四下的意思是扩展选择了三次,而不是「选中该段」的意思。

    所以问题来了,有的段落,只有一个词,比如「是」,或者「小姐」,在这样的情况下,只要按三下「F8」就能选中全段(因为按第二下的时候,同时完成了「扩展到本词」和「扩展到本段除换行符外的内容」),那么,在这种只有一个词的段落中,按四下「F8」的结果就是选中了全文,而非全段。

    计算机的自动操作就失控了。

    所以我的解决办法,是先不管青红皂白,在每一段结尾加上「时临具工」四个字,这样就不会存在「只有一个词」的段落了,所以「按四下『F8』」的结果永远是「选中该段」,不会误成「选中全文」了。

    等处理完了,再自动把所有「时临具工」删除。

    之所以用这四个字,是提醒自己这是个「临时工具」。

    之所以不用「临时工具」,是因为,如果文稿里恰好本来也有这四个字的话,会被误删的。​

    然后我叫计算机做的事情是这样的:

    4.2.1、 在每一行的结尾加上「时临具工」四个字。

    4.2.2、 把第一个单元格(原文)里的第一段剪切下来粘贴到第二个单元格(译文)开头

    4.2.3、 把对应的原文和译文之间那个换行符替换成文字「符行换」

    / 于是原文和译文由两段变成了一段,由「符行换」三个字间隔开:一方面两者同在一段,「选择本段」的功能可以方便使用;另一方面,「符行换」三个字在操作结束后可以「全部替换」为真正的换行符,恢复原文与译文在相邻两段的空间关系。

    4.2.4、把已成一段的原文和译文剪切下来,粘贴到文档末尾(表格之外)

    循环操作之后,表格里的原文和译文越剪越少,在文档末尾渐渐累积起来。最后把「时临具工」统一删除,再把「符行换」全部替换为真正的换行符,再删除作为临时工具的两个表格就行了。

    代码是这样的——


    Sub 中英文对照稿解决办法() ' ' 中英文对照稿解决办法 宏 ' ' Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "^p" .Replacement.Text = "时临具工^p" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchKashida = False .MatchDiacritics = False .MatchAlefHamza = False .MatchControl = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With '在每一行的行尾加四个字:「时临具工」。 Selection.Find.Execute Replace:=wdReplaceAll Selection.HomeKey Unit:=wdStory Selection.Extend Selection.Extend Selection.Extend Selection.Extend Selection.Cut Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.PasteAndFormat (wdFormatOriginalFormatting) '把第一个单元格(原文)里的第一段剪切下来粘贴到第二个单元格(译文)开头 Selection.TypeBackspace Selection.TypeText Text:="符行换" '把对应的原文和译文之间那个换行符替换成文字「符行换」,于是原文和译文由两段变成了一段,由「符行换」三字间隔开 Selection.Extend Selection.Extend Selection.Extend Selection.Extend Selection.Cut '把这一段「原文 +『符行换』+ 译文」剪切下来 Selection.EndKey Unit:=wdStory Selection.PasteAndFormat (wdFormatOriginalFormatting) '粘贴到文档末尾 Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=3 Selection.HomeKey Unit:=wdStory, Extend:=wdExtend '选择第一个单元格(原文)中的内容,以便测量内容的长度,来判断是否原文都被剪切完了 Do While Len(Selection) > 3 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.Extend Selection.Extend Selection.Extend Selection.Extend Selection.Cut Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.PasteAndFormat (wdFormatOriginalFormatting) Selection.TypeBackspace Selection.TypeText Text:="符行换" Selection.Extend Selection.Extend Selection.Extend Selection.Extend Selection.Cut Selection.EndKey Unit:=wdStory Selection.PasteAndFormat (wdFormatOriginalFormatting) Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=3 Selection.HomeKey Unit:=wdStory, Extend:=wdExtend Loop '循环操作,直到第一个单元格内的原文剪切完毕 Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "时临具工" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchKashida = False .MatchDiacritics = False .MatchAlefHamza = False .MatchControl = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With '删除所有「时临具工」 Selection.Find.Execute Replace:=wdReplaceAll With Selection.Find .Text = "符行换" .Replacement.Text = "^p" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchKashida = False .MatchDiacritics = False .MatchAlefHamza = False .MatchControl = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll '将所有文字「符行换」替换成真正的换行符 Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveDown Unit:=wdLine, Count:=1 Selection.HomeKey Unit:=wdStory, Extend:=wdExtend Selection.Rows.Delete Selection.TypeBackspace '删除作为临时工具的两个单元格 End Sub

    需要留意的是,如果文稿较长,「宏」的处理时间会比较长,CPU 和内存的占用比较高,甚至 Word 会出现「程序无法响应」的伪死机现象,需要耐心等待好几分钟(或者可能更长时间,视具体情况而定)才能让整个过程自动完成。

    非常有意思的地方是,我在处理这个问题时,首先想到的是上面这个处理办法。尤其是「符行换」的处理办法,算得上挺古怪的。

    后来我才想起来明明有更简单的处理办法,不用像上面这样剑走偏锋:

    4.3.1、把上面表格(原文)里的第一段剪切粘贴到文档尾;

    4.3.2、把下面表格(译文)里的第一段剪切粘贴到文档尾;

    这两步不断循环就可以了。

    根本不需要什么「符行换」。

    所以就有了第二个解决方案,代码简洁了不少:


    Sub 中英文对照解决方案 2() ' ' 中英文对照解决方案 2 宏 ' ' Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "^p" .Replacement.Text = "^p 时临具工" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchKashida = False .MatchDiacritics = False .MatchAlefHamza = False .MatchControl = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=3 Selection.HomeKey Unit:=wdStory, Extend:=wdExtend Do While Len(Selection) > 3 Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.Extend Selection.Extend Selection.Extend Selection.Extend Selection.Cut Selection.EndKey Unit:=wdStory Selection.PasteAndFormat (wdFormatOriginalFormatting) Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=1 Selection.Extend Selection.Extend Selection.Extend Selection.Extend Selection.Cut Selection.EndKey Unit:=wdStory Selection.PasteAndFormat (wdFormatOriginalFormatting) Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveLeft Unit:=wdCharacter, Count:=3 Selection.HomeKey Unit:=wdStory, Extend:=wdExtend Loop Selection.HomeKey Unit:=wdStory Selection.MoveRight Unit:=wdCell Selection.MoveDown Unit:=wdLine, Count:=1 Selection.HomeKey Unit:=wdStory, Extend:=wdExtend Selection.Rows.Delete Selection.TypeBackspace Selection.Find.ClearFormatting Selection.Find.Replacement.ClearFormatting With Selection.Find .Text = "时临具工" .Replacement.Text = "" .Forward = True .Wrap = wdFindContinue .Format = False .MatchCase = False .MatchWholeWord = False .MatchKashida = False .MatchDiacritics = False .MatchAlefHamza = False .MatchControl = False .MatchByte = True .MatchAllWordForms = False .MatchSoundsLike = False .MatchWildcards = False .MatchFuzzy = False End With Selection.Find.Execute Replace:=wdReplaceAll End Sub

    最最有趣的地方来了。

    第一个方案,我脑海里冒出的第一个念头,一个曲折繁琐的复杂方案,在我的电脑上对《新还珠格格》第一集进行文字整理的过程,花了 5 分 41 秒

    第二个方案,正常人应该首先想到的念头,一个短小精悍的解决方案,在我的电脑上对《新还珠格格》第一集进行文字整理的过程,花了5 分 58 秒

    这真是一种「你不理解的快乐」。

    -

    阅读原文
    精选评论

    @王淡真



    最后的剧本例子,其实用 Excel 更快。中文单独一列,用回车分隔,每行都是一段。编号为 1,3,5,7......英文类似,不过编号是 2,4,6,8。然后把英文剪切到中文后面,按编号排序即可。当然,如果要经常这么处理,还是写宏方便。

    文章中有一处谬误,其实 Word 是可以操作多份文件的,每个打开的 Word 文档都是一个 Document 对象,只要获取文件名就可以操作它,比如切换到已打开的「中文字幕.doc」:
    Documents("中文字幕.doc").Activate

    另外,使用 Application.ScreenUpdating = False 把屏幕更新关掉,可以大大加快宏的执行速度,此时看不到 Word 的操作动作。
     
正在加载...