You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Haskell按出现顺序检索文本文件内标记函数并完成编辑

现有代码的核心问题

  • Haskell是纯函数式编程语言,没有可变变量和命令式for循环结构,你写的类Python嵌套循环+全局计数变量的逻辑无法直接在Haskell中运行
  • 预统计标记数量的思路不可行:table和figure的编号依赖其前方最近的section编号,必须按文本顺序逐段遍历处理标记,不能分开统计后批量替换
  • 类型不匹配:hGetContents返回的是String类型,而你调用的Data.Text的count函数仅能处理Text类型,二者混用会直接报错
  • 缺失参数提取逻辑:当前代码仅做了关键词匹配,没有实现从标记中提取{title}、{id}参数的步骤

实现思路

采用带状态的遍历方案实现需求,维护三个可变状态:

  • 当前section编号
  • 当前section下的table编号
  • 当前section下的figure编号
    遍历文本时每匹配到一个标记就更新对应状态,同时生成替换后的文本内容。

可运行参考实现

首先在项目cabal配置中添加text、parsec、mtl依赖,代码如下:

{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import System.Environment (getArgs)
import Text.Parsec
import Text.Parsec.Text

-- 定义编号状态结构
data NumState = NumState
  { secNo :: Int
  , tabNo :: Int
  , figNo :: Int
  } deriving (Show)

-- 初始状态:所有编号从0开始计数
initState :: NumState
initState = NumState 0 0 0

-- 标记类型定义
data Markup = Section T.Text T.Text
            | Table T.Text T.Text
            | Figure T.Text T.Text
            | PlainText T.Text
            deriving (Show)

-- 标记解析器
parseMarkup :: Parser [Markup]
parseMarkup = many $ choice
  [ try parseSection
  , try parseTable
  , try parseFigure
  , parsePlain
  ]
  where
    braced :: Parser T.Text
    braced = between (char '{') (char '}') (T.pack <$> many (noneOf "}"))
    parseSection = do
      string "\\Section"
      title <- braced
      id <- braced
      return $ Section title id
    parseTable = do
      string "\\table"
      title <- braced
      id <- braced
      return $ Table title id
    parseFigure = do
      string "\\figure"
      title <- braced
      id <- braced
      return $ Figure title id
    parsePlain = PlainText . T.singleton <$> anyChar

-- 处理单个标记、更新状态、生成输出文本
processMarkup :: (NumState, T.Text) -> Markup -> (NumState, T.Text)
processMarkup (st, acc) markup = case markup of
  Section title _ ->
    let newSec = secNo st + 1
        -- 新section出现时重置table和figure计数
        newSt = st { secNo = newSec, tabNo = 0, figNo = 0 }
        out = T.append acc $ "Section " <> T.pack (show newSec) <> ": " <> title
    in (newSt, out)
  Table title _ ->
    let newTab = tabNo st + 1
        newSt = st { tabNo = newTab }
        sec = secNo st
        out = T.append acc $ "Table " <> T.pack (show sec) <> "." <> T.pack (show newTab) <> ": " <> title
    in (newSt, out)
  Figure title _ ->
    let newFig = figNo st + 1
        newSt = st { figNo = newFig }
        sec = secNo st
        out = T.append acc $ "Figure " <> T.pack (show sec) <> "." <> T.pack (show newFig) <> ": " <> title
    in (newSt, out)
  PlainText t -> (st, T.append acc t)

main :: IO ()
main = do
  args <- getArgs
  if null args
    then putStrLn "请传入要处理的文件路径作为运行参数"
    else do
      content <- TIO.readFile (head args)
      case parse parseMarkup "" content of
        Left err -> print err
        Right markups -> do
          let (_, result) = foldl processMarkup (initState, "") markups
          TIO.writeFile (head args ++ ".out") result
          putStrLn "处理完成,输出文件为原文件名加.out后缀"

扩展说明

ref标记的处理逻辑与上述逻辑一致,你只需新增对应的ref解析分支,再新增一个独立的ref编号状态字段即可。如果不需要保留原文本的非标记内容,可自行调整parsePlain的输出逻辑。

内容的提问来源于stack exchange,提问作者Dennis Gaita

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 12:54:02