Haskell: переход через строковый/текстовый файл

Я пытаюсь прочитать файл сценария, затем обработать и вывести его в файл html. В моем файле сценария всякий раз, когда есть @title(это заголовок), я добавляю тег [header] это заголовок [/header] в свой html выход. Итак, мой подход заключается в том, чтобы сначала прочитать файл сценария, записать содержимое в строку, обработать строку, а затем записать строку в файл html.

В другом случае, чтобы распознать @title, мне нужно будет прочитать символ за символом в строке. Когда я читаю «@», мне нужно будет определить следующий символ, чтобы увидеть, являются ли они заголовком.

ВОПРОС: Как мне пройти через строку (которая представляет собой список символов) в Haskell?


person Charlie Victor    schedule 16.02.2013    source источник
comment
Написать парсер. Вы можете использовать другие лайфхаки, более простые в краткосрочной перспективе, но позже вы пожалеете об этом.   -  person Daniel Wagner    schedule 16.02.2013
comment
Что касается парсеров, Parsec владеет.   -  person Cat Plus Plus    schedule 16.02.2013
comment
@CatPlusPlus Это спорно. По производительности Attoparsec часто может превосходить его.   -  person Nikita Volkov    schedule 16.02.2013


Ответы (3)


Вы можете использовать простой прием рекурсии, например

findTag [] = -- end of list code.
findTag ('@':xs)
  | take 5 xs == "title" = -- your code for @title
  | otherwise            = findTag xs
findTag (_:xs) = findTag xs

так что в основном вы просто сопоставляете шаблон, если следующий символ (голова списка) равен «@», а затем вы проверяете, образуют ли следующие 5 символов «заголовок». если это так, вы можете продолжить свой код синтаксического анализа. если следующий символ не '@', вы просто продолжаете рекурсию. Когда список пуст, вы достигаете первого совпадения с образцом.

У кого-то может быть лучшее решение.

Надеюсь, это ответит на ваш вопрос.

редактировать:

Для большей гибкости, если вы хотите найти конкретный тег, вы можете сделать это:

findTag [] _ = -- end of list code.
findTag ('@':xs) tagName
  | take (length tagName) xs == tagName = -- your code for @title
  | otherwise = findTag xs
findTag (_:xs) _ = findTag xs

Таким образом, если вы делаете

findTag text "title"

Вы будете специально искать заголовок, и вы всегда можете изменить имя тега на любое другое.

Другое редактирование:

findTag [] _ = -- end of list code.
findTag ('@':xs) tagName
  | take tLength xs == tagName = getTagContents tLength xs
  | otherwise = findTag xs
  where tLength = length tagName
findTag (_:xs) _ = findTag xs

getTagContents :: Int -> String -> String
getTagContents len = takeWhile (/=')') . drop (len + 1) 

если честно, это становится немного запутанным, но вот что происходит:

Сначала вы опускаете длину tagName, затем еще одну для открывающей скобки, а затем завершаете с помощью takeWhile, чтобы доставить символы до закрывающей скобки.

person Attic    schedule 16.02.2013
comment
Спасибо за ваше предложение! - person Charlie Victor; 16.02.2013

Очевидно, ваша проблема относится к категории синтаксического анализа. Как мудро заметил Даниэль Вагнер, по соображениям удобства сопровождения вам гораздо лучше подходить к нему в целом с помощью синтаксического анализатора.

Другое дело, если вы хотите эффективно работать с текстовыми данными, вам лучше использовать Text вместо String.

Вот как вы можете решить свою проблему, используя библиотеку анализатора Attoparsec:

-- For autocasting of hardcoded strings to `Text` type
{-# LANGUAGE OverloadedStrings #-}

-- Import a way more convenient prelude, excluding symbols conflicting 
-- with the parser library. See
-- http://hackage.haskell.org/package/classy-prelude
import ClassyPrelude hiding (takeWhile, try)
-- Exclude the standard Prelude
import Prelude ()
import Data.Attoparsec.Text

-- A parser and an inplace converter for title
title = do
  string "@title("
  r <- takeWhile $ notInClass ")"
  string ")"
  return $ "[header]" ++ r ++ "[/header]"

-- A parser which parses the whole document to parts which are either
-- single-character `Text`s or modified titles
parts = 
  (try endOfInput >> return []) ++
    ((:) <$> (try title ++ (singleton <$> anyChar)) <*> parts)

-- The topmost parser which concats all parts into a single text
top = concat <$> parts

-- A sample input
input = "aldsfj@title(this is a title)sdlfkj@title(this is a title2)"

-- Run the parser and output result
main = print $ parseOnly top input

Это выводит

Right "aldsfj[header]this is a title[/header]sdlfkj[header]this is a title2[/header]"

P.S. ClassyPrelude переопределяет ++ как псевдоним для mappend из Monoid, поэтому вы можете заменить его на mappend, <> или <|> из Alternative, если хотите.

person Nikita Volkov    schedule 16.02.2013

Для поиска и замены шаблона вы можете использовать streamEdit.

import Replace.Megaparsec
import Text.Megaparsec
import Text.Megaparsec.Char

title :: Parsec Void String String
title = do
    void $ string "@title("
    someTill anySingle $ string ")"

editor t = "[header]" ++ t ++ "[/header]"

streamEdit title editor " @title(this is a title) "
" [header]this is a title[/header] "
person James Brock    schedule 31.08.2019