Краткое описание типа
XmlTree = NTree XNode
означает, что каждый XmlTree
имеет конструкцию
NTree XNode [XMLTree]
Где первый аргумент - текущий узел, а второй аргумент - список дочерних элементов.
Творческий процесс
processTopDown
примет преобразование дерева, которое вы предоставляете, и произведет преобразование дерева, которое применяет его рекурсивно.
Сначала давайте определим преобразование дерева, которое вы хотите на одном узле:
- Пройти через дочерние узлы текущего узла
- Если они совпадают с указанными нами тегами, то
- Возьмите всех детей тега и
- Вместо этого сделайте их потомками текущего узла
- Затем удалите тег
Преобразование не «поднимает» дочерние элементы текущего узла, потому что это не было бы возможно для корня.
Хороший способ сделать это - использовать processChildren
- стрелку, которая позволяет нам указывать новых дочерних элементов для текущего узла на основе старых дочерних элементов. Для этого нам понадобится условные стрелки
Затем мы можем разделить дизайн на две части: предикат для сопоставления желаемых тегов и преобразование, которое мы хотим выполнить
Предикат
Напоминаем себе о , какие формы может принимать XNode
, тот, который нас интересует,
XTag QName XmlTrees
Мы хотим сопоставить узлы этой формы с заданными нами именами тегов. Для этого напишем вспомогательную функцию
filterOnQName :: QName -> XNode -> Bool
filterOnQName qname (XTag xqname _)
| qname == xqname = True
| otherwise = False
filterOnQName _ _ = False
Для простоты использования мы хотим записать теги в виде строк, поэтому мы будем использовать mkName
для преобразования их в QName
s. Тогда наша более полезная функция фильтра -
filterTags :: [String] -> XmlTree -> Bool
filterTags tagNames (NTree xnode _) = any (\qname -> filterOnQName qname xnode) (map mkName tagNames)
Но это не стрелка, которой мы должны быть по причинам, которые мы увидим позже. Мы можем просто превратить его в один с isA
childFilter tags = isA (filterTags tags)
Преобразование
Нам понадобятся две стрелки для тела преобразования - одна для случая, когда фильтр соответствует, и одна для того, когда он не подходит.
Когда это не так, преобразование легко - мы хотим сохранить текущий узел.
filterFailed = this
Здесь this
- стрелка идентификации - она ничего не делает.
Когда фильтр соответствует, мы хотим получить потомков - сначала давайте напишем помощника
getChildren :: XmlTree -> [XmlTree]
getChildren (NTree _ children) = children
Удобно, поскольку мы работаем со стрелками списка, мы можем превратить это прямо в стрелку, используя arrL
liftChildren = arrL getChildren
Объединяя их
Теперь мы можем превратить это в одну стрелку, используя ifA
, версию стрелки if
liftMatchedChildren tags = ifA (childFilter tags) liftChildren filterFailed
И, наконец, мы можем описать преобразование, которое мы хотели
processRootElement
= processTopDown (processChildren (liftMatchedChildren ["mi", "mrow"]))