Unusual title prefix

Hello,

I currently use <#hn>. for the title prefix for my manuscript to produce level numbering in the format 1.1.1.1. My publisher requires me to use a format, Hn, where n is the level of the heading, in this case H4.

Is there an easy way to do that in Scrivener? If not, could I apply a regex like (\d.?){n} to Hn?

Thank You,
Michael

Last first: Not sure about regexes in compile. Regex is supposed to be usable in search, but I’m not sure about replace, and I’m definitely not sure about compile.

Easy way? No. I can think of several ways to do this, but they’re all rather fragile and require substantial amounts of tedium that’s easy to get wrong.

To make one correction, regular expressions (and simpler string matching) can absolutely be done during the compile process, in a non-destructive way on the source text, and upon a certain degree of material generated by the compiler as well, such as heading text, separators, etc. Replacements and placeholders share a somewhat uneasy common ground with one another, since replacements can be used to create longer more complex placeholders. So it’s worth experimenting to see whether the interaction you want will work. I use small test projects for this, which compile quickly.

In this case, Replacements can modify the result that is produced by the <$hn> placeholder. You can get, “H1” out of “1.”, using “(\d+).” ⇒ “H$1”, for instance.

So while regex exists and works fine, I don’t know how helpful that will be toward solving this particular problem. The hn placeholder only indicates depth through the length of the sequence of hierarchical counters within it rather than as a number that can be extracted from it. I may not be aware of a trick, but I don’t believe regex has a way of saying that there were three matches to a pattern in this line, thus print 3. In most cases you’re going to need a programming language behind the regex to do something like that.

So the above is probably all academic. I can’t think a way to contort any of Scrivener’s available placeholders or auto-counters into a notion of outline depth.

Moving along to solutions: I don’t think this is a particularly difficult problem. Scrivener is after all an outliner, with a keen awareness of depth, with a multi-level type system. Take a look at the “Novel (with Parts)” template, for example. If you open up Project ▸ Project Settings… and open the “Default Types by Structure” tab, in Section Types, you’ll find a degree of depth-based typing going on. You’ll probably want something like that in your project, though perhaps to a greater degree, with “Section” and “Subsection” falling under the existing part/chapter types. You can make as many as you want, call them what you will, and assign them as deep as you need.

The nice thing about this approach is that the compiler can be set to use however much of that structure you want. You don’t need to use all four or five levels of depth individually for proofing, you could just make do with an assignment setup that binds all of those levels to one single Section Layout that prints with <$hn>. But for your publisher, you will want a special Format set up that has four or five different section layouts, each with their own Title Prefix typed in, “H1”, “H2”, etc.

Again, just think of it in terms of what the novel template does. Instead of printing “PART” in huge font text we’re printing “H1” beside the title. Instead of “CHAPTER TWENTY-FIVE” we’re printing “H2”… that’s all that’s going on and it’s no more complicated than that.

I think finding the level can be done with regex, but it’s a relatively complicated expression using multiple if statements. 5 periods, you’re at level 6. no periods, you’re at level 1.

After a test, I discovered something.

Replacements follows Section Layouts in the Compile order. This is the only reason this is possible at the moment. If I add a Title prefix, Replacements will replace it if I specify a replacement, and replacements can use regex.

You’ll need to add 6 replacements (assuming you have only 6 header levels). They should be in REVERSE order; that is, match the 5 period expression, replace with H6; match the 4 period expression, replace with H5. The last one will have to use a slightly different regex.

Something must terminate H<$hn>. Ths should be a set of characters not used elsewhere in the text (this is the fragile part of this one. Anything in the text that matches the expression will be replaced, but if we add, say “HH!” to the title prefix and use it as part of our match, that issue should be solved). The expression in the prefix should not be duplicated by any text in the documents.

If we only check for periods, a single period in the text could match at Level 2, so we need to check not just periods but #'s as well, and we MUST have something in the prefix to be matched that won’t happen in the rest of the text, so the thing doesn’t replace 8.34512304987 with H2.

Title prefix becomes H<$hn>HH! .

The regex to match Level 2 is [H]\d+[.]\d+[H][H][!]

Possible. Easy, perhaps. Less fragile than most of the other ideas I had. Solved. I think.

Thank you. This macro works in Word:

Sub ConvertHeading()
'
' Converts
'    3.2.1.3. My header
' To
'    H4 My header
' With the proper formatting
' At depth greater than minDepth, it is converted to
'    My header
'
    Dim iDepth As Integer, iStep As Integer, minDepth As Integer
    Dim sFmt As String, sTgt As String
        
    ' Below minDepth, no formatting or H-code will be used
    minDepth = 4
    Selection.Find.ClearFormatting
    Selection.Find.Replacement.ClearFormatting
    
    ' Loop through depth
    For iDepth = 8 To 1 Step -1
    
        ' Style Formatting
        If iDepth <= minDepth Then
            Selection.Find.Replacement.Style = ActiveDocument.Styles("Heading " & CStr(iDepth))
        End If
        
        ' Search String
        sFmt = "^#."
        For iStr = 2 To iDepth
            sFmt = sFmt & "^#."
        Next iStr
        
        ' Replacement String
        If iDepth <= minDepth Then
            sTgt = "H" & CStr(iDepth) & " "
        Else
            sTgt = ""
        End If
        
        ' Find and replace
        With Selection.Find
        .Text = sFmt & " "
        .Replacement.Text = sTgt
        .Forward = True
        .Wrap = wdFindContinue
        .Format = True
        .MatchCase = False
        .MatchWholeWord = False
        .MatchWildcards = False
        .MatchSoundsLike = False
        .MatchAllWordForms = False
        End With
        Selection.Find.Execute Replace:=wdReplaceAll
    Next iDepth
End Sub