Итерация в Ant, вызов функции xquery из командной строки

У меня есть xml-файл - назовите его myXML.xml - вот так:

<?xml version="1.0" encoding="UTF-8"?>
<Metrics info1="1" info2="2" info3="3" xmlns="http://metrics.sourceforge.net/2003/Metrics-First-Flat">
    <Metric id = "NORM" description ="Number of Overridden Methods">
      <Values per = "type" total = "135" avg = "0.452" stddev = "0.94" max = "5">
        <Value name="a" source ="a.java" package ="package.a" value ="1"/>
        <Value name="b" source ="b.java" package ="package.b" value ="34"/>
        <Value name="c" source ="c.java" package ="package.c" value ="4"/>
        <Value name="d" source ="d.java" package ="package.d" value ="99"/>
        <Value name="e" source ="e.java" package ="package.e" value ="99"/>
        <Value name="f" source ="f.java" package ="package.f" value ="99"/>
        <Value name="g" source ="g.java" package ="package.g" value ="99"/>
      </Values>
    </Metric>

    <Metric id = "NOI" description ="Number of Overridden Methods">
      <Values per = "type" total = "135" avg = "0.452" stddev = "0.94" max = "5">
        <Value name="a" source ="a.java" package ="package.a" value ="10"/>
        <Value name="b" source ="b.java" package ="package.b" value ="340"/>
        <Value name="c" source ="c.java" package ="package.c" value ="40"/>
        <Value name="d" source ="d.java" package ="package.d" value ="990"/>
      </Values>
    </Metric>
</Metrics>

Потому что я должен оценить десятки таких файлов (например, myXML.xml) более десятка атрибутов (здесь id=NORM а также id=NOI) Я пытался автоматизировать это в Apache Ant.

В лучшем случае было бы получить фиксированный файл (myXML.xml) CSV-файл в ответ - который будет сохранен как myXML.csv - и выглядит примерно так

NORM 1, 34, 4, 99, 99, 99, 99
NOI 10, 340, 40, 990

Чтобы подойти к этому, я подумал создать файл свойств <property file="metrics.properties"/> который выглядит как

p_1 = NORM
p_2 = NOI
...
p_N = VG

где N произвольно, поэтому Ant должен выяснить, N (в небольшом примере здесь N=2) и создайте CSV-файл, как указано выше, над всеми p_i's, Далее, я думаю, мне следует переписать приведенный ниже xquery как функцию файла (myXML.xml) а также NORM и запустите его из командной строки. Но я не понимаю, как это сделать.

Следующий xquery частично делает то, что мне интересно:

declare option db:stripns 'true';
for $x in doc("myXML.xml")/Metrics/Metric[@id="NORM"]/Values//Value/@value
return data($x)

но оба myXML.xml а также NORM фиксированы и выход просто 1 34 4 99 99 99 99 . Я сохранил этот файл в query.xq и запустил его в Ant:

<target name="ant" depends="#1">
 <echo> ant </echo>
 <exec executable="${pathToAnt}/basex.bat" dir="${basedir}" error="${basedir}/output/error.txt">
  <arg value = "query.xq"/> 
  <redirector output="${basedir}/output/myXML.csv" alwayslog="true"/>
 </exec>
</target>

Это то, что у меня есть - немного далеко от того, что я собираюсь получить.

Надеюсь, понятно, чего я пытаюсь достичь. Я новичок в xquery, а также в ant, и я использую BaseX (не обязательно) под Windows, поэтому это довольно сложно для меня;-).

Большое спасибо за любую помощь, советы, вопросы и т. Д.

1 ответ

Решение

Спасибо за вашу помощь. Я понял:

Цикл for можно выполнить с помощью http://ant-contrib.sourceforge.net/tasks/tasks/for.html. Я сделал итерацию по всем моим исходным файлам (их имена хранятся в fileNames) который выглядит как

<for list="${fileNames}" delimiter="," param="nameIter">
 <sequential>
  <echo> loop over fileNames: nameIter=@{nameIter} </echo>
  <exec executable="${pathToAnt}/basex.bat" dir="${basedir}" error="${basedir}/output/error_baseX/@{nameIter}Error.txt">
   <arg value="-b$importList=${metricsList}" />
   <arg value="-b$name=@{nameIter}"/>
   <arg value="./source_data/data/query.xq"/>
   <redirector output="${basedir}/output/@{nameIter}.csv" alwayslog="true"/>
  </exec>
 </sequential>
</for>

Теперь exec-часть запускает следующий xquery из командной строки, где переменная metricsList состоят из всех метрик, которые меня интересуют. Например, в приведенном выше XML-коде это будет metricsList=NORM,NOI, Файл xquery query.xq является

declare option db:stripns 'true';
declare variable $name external;
declare variable $importList external;
declare variable $list as xs:string* := tokenize($importList, ',');
for $i in $list
let $x := doc($name)/Metrics/Metric 
let $nl := "&#10;" (: this is a newline:)
return ($nl,data($x[@id=$i]/Values/../@id), data($x[@id=$i]/Values/Value/@value))

Я вижу, этому уже почти пять лет, но для любого, кто придет позже с похожим вопросом, этот способ может быть решен только с помощью XQuery без Ant.

Это должно зависеть от процессора (здесь я использую BaseX), если процессор поддерживает файловый модуль EXPath (основные из них). Может быть, что collection() Функция ведет себя по-разному, BaseX либо читает все файлы XML, которые он находит в каталоге (это метод, который мы здесь используем), либо интерпретирует путь как путь в своей собственной внутренней базе данных.

Поскольку XML имеет именованное пространство имен ("http://metrics.sourceforge.net/2003/Metrics-First-Flat") мы должны признать это в наших выражениях XPath. Есть два способа сделать это: мы можем объявить пространство имен по умолчанию для элементов в прологе (наш подход здесь) или просто добавить подстановочный знак префикса перед именем каждого элемента в нашем выражении XPath (*:Values/*:Value).

Поскольку результатом будет последовательность строк (и нам нужна одна строка для нашего CSV), мы объединяем сегменты и добавляем буквальную запятую в конце концов, кроме последнего сегмента, с помощью небольшой встроенной функции, составляем финальную строку с помощью string-join() и записать CSV на диск.

declare default element namespace "http://metrics.sourceforge.net/2003/Metrics-First-Flat";

let $path := "/path/to/folder/with/XML/files/"
let $docs := collection($path)
let $decorate := function($sequence) {
  for $i in subsequence($sequence, 1, count($sequence) - 1)
    return $i || ","
 ,subsequence($sequence, count($sequence))
}
for $doc in $docs/Metrics
count $cnt                       (: this helps to create sequential file names:)
let $norm := ( "NORM",
               for $metric in $doc/Metric[@id="NORM"]
               return $metric/Values/Value/@value/data()
             )
let $noi := ( "NOI",
              for $metric in $doc/Metric[@id="NOI"]
              return $metric/Values/Value/@value/data()
            )
return
  file:write(
    concat("/path/to/file-", $cnt, ".csv")
   ,concat(
    string-join($decorate($norm))
   ,out:nl()                      (: BaseX specific, creates a 'newline' :)
   ,string-join($decorate($noi))
  ))
Другие вопросы по тегам