2008-10-02 3 views
3

私はたくさんのXMLファイルを持っており、それらからレポートを生成したいと思います。以下のようなレポートが情報を提供する必要があります:すべての文書は、ルート要素を持っていることを意味多くのXMLを分析するプログラム

root 100% 
a*1 90% 
b*1 80% 
    c*5 40% 

、90%は、80%が根で1つのB要素を持っているルートに1つの要素を持っている、40% 5 cの要素はbです。 、

c*4.3 4 6 40% 

は、40%が4と6つのC要素の間に持っていることを意味します。たとえば、いくつかの文書が4つのC要素を持っている場合は

は、いくつかの5及び一部6は、それが何かのように言う必要があります平均は4.3です。

私はフリーソフトウェアを探していますが、存在しない場合は書きます。私はそれをしようとしていたが、それを確認することを考えた。 XMLファイルの数千を分析して構造の概要を取得する必要があるのは、最初の人ではないかもしれません。

答えて

11

ここにXSLT 2.0メソッドがあります。

には、スキャンするドキュメントノードのシーケンスが含まれていると仮定し、ドキュメントに表示される各要素に対して1行を作成するとします。あなたはそれを行うために<xsl:for-each-group>を使用することができます。

<xsl:for-each-group select="$docs//*" group-by="name()"> 
    <xsl:sort select="current-group-key()" /> 
    <xsl:variable name="name" as="xs:string" select="current-grouping-key()" /> 
    <xsl:value-of select="$name" /> 
    ... 
</xsl:for-each-group> 

その後、あなたは文書の中で、その要素の統計情報を知りたいです。今

<xsl:variable name="elem-counts" as="xs:integer+" 
    select="$docs-with/count(//*[name() = $name])" /> 

そして:

<xsl:variable name="docs-with" as="document-node()+" 
    select="$docs[//*[name() = $name]" /> 

第二に、あなたは文書のそれぞれにその名前の要素数のシーケンスを必要とする:まず、文書はその中にその名前の要素を持っている見つけますあなたは計算を行うことができます。平均、最小および最大は、avg(),min()およびmax()関数で計算できます。パーセンテージは、単純に要素を含む文書の数を、文書の総数で割った値です。それを一緒に置く

<xsl:for-each-group select="$docs//*" group-by="name()"> 
    <xsl:sort select="current-group-key()" /> 
    <xsl:variable name="name" as="xs:string" select="current-grouping-key()" /> 
    <xsl:variable name="docs-with" as="document-node()+" 
    select="$docs[//*[name() = $name]" /> 
    <xsl:variable name="elem-counts" as="xs:integer+" 
    select="$docs-with/count(//*[name() = $name])" /> 
    <xsl:value-of select="$name" /> 
    <xsl:text>* </xsl:text> 
    <xsl:value-of select="format-number(avg($elem-counts), '#,##0.0')" /> 
    <xsl:text> </xsl:text> 
    <xsl:value-of select="format-number(min($elem-counts), '#,##0')" /> 
    <xsl:text> </xsl:text> 
    <xsl:value-of select="format-number(max($elem-counts), '#,##0')" /> 
    <xsl:text> </xsl:text> 
    <xsl:value-of select="format-number((count($docs-with) div count($docs)) * 100, '#0')" /> 
    <xsl:text>%</xsl:text> 
    <xsl:text>&#xA;</xsl:text> 
</xsl:for-each-group> 

を私はここに行っていない何が要素の深さに応じて行をインデントされます。私はちょうどあなたに統計を与えるためにアルファベット順に要素を注文しました。その理由は2つあります。第1に、異なる文書が異なる構造を持つため、要素統計を文書にどのように表示するかを反映する何らかの構造で要素統計を表示することは、はるかに難しくなります。第2に、多くのマークアップ言語では、文書の正確な構造を知ることができません(たとえば、セクションが任意の深さまでセクション内にネストできるため)。

私はそれが役に立たないことを望みます。

UPDATE:

は、XSLTのラッパーとXSLTを実行するためのいくつかの命令が必要ですか? OK。まず、Saxon 9Bに手をつけてください。

分析するすべてのファイルをディレクトリに配置する必要があります。 Saxonでは、special URI syntaxを使用するコレクションを使用して、そのディレクトリ(またはそのサブディレクトリ)内のすべてのファイルにアクセスできます。再帰的に検索したり、ファイル名で見ているファイルをフィルタリングする場合は、その構文を調べることをお勧めします。今

フルXSLT:これはdirを設定するために、mainという名前のテンプレートを使用してプロセスを開始するためにサクソンを伝え

 
> java -jar path/to/saxon.jar -it:main -o:report.txt dir=file:///path/to/your/directory?select=*.xml 

<xsl:stylesheet version="2.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    exclude-result-prefixes="xs"> 

<xsl:param name="dir" as="xs:string" 
    select="'file:///path/to/default/directory?select=*.xml'" /> 

<xsl:output method="text" /> 

<xsl:variable name="docs" as="document-node()*" 
    select="collection($dir)" /> 

<xsl:template name="main"> 
    <xsl:for-each-group select="$docs//*" group-by="name()"> 
    <xsl:sort select="current-group-key()" /> 
    <xsl:variable name="name" as="xs:string" select="current-grouping-key()" /> 
    <xsl:variable name="docs-with" as="document-node()+" 
     select="$docs[//*[name() = $name]" /> 
    <xsl:variable name="elem-counts" as="xs:integer+" 
     select="$docs-with/count(//*[name() = $name])" /> 
    <xsl:value-of select="$name" /> 
    <xsl:text>* </xsl:text> 
    <xsl:value-of select="format-number(avg($elem-counts), '#,##0.0')" /> 
    <xsl:text> </xsl:text> 
    <xsl:value-of select="format-number(min($elem-counts), '#,##0')" /> 
    <xsl:text> </xsl:text> 
    <xsl:value-of select="format-number(max($elem-counts), '#,##0')" /> 
    <xsl:text> </xsl:text> 
    <xsl:value-of select="format-number((count($docs-with) div count($docs)) * 100, '#0')" /> 
    <xsl:text>%</xsl:text> 
    <xsl:text>&#xA;</xsl:text> 
    </xsl:for-each-group> 
</xsl:template> 

</xsl:stylesheet> 

そして、それを実行するようにあなたが何かをするだろうパラメータをfile:///path/to/your/directory?select=*.xmlに設定し、出力をreport.txtに送信します。

+0

Jeni、あなたのサイトがXPathの大きな課題に私を助けてくれたので、私はあなたの大ファンです。 StackOverflowでここに参加してくれてうれしいです。 –

+0

あなたのアップデートを見ました - 今すぐ入手できます:) –

0

[ここコミュニティポスト:いいえカルマ関与;)]
私はここにcode-challengeを提案:

はxmlfiles.com/examplesで見つけて、次の出力を思い付くしようとするすべてのXMLを解析:それは私の非常に最初のルビーPROGRAあるので、ここで

Analyzing plant_catalog.xml: 
Analyzing note.xml: 
Analyzing portfolio.xml: 
Analyzing note_ex_dtd.xml: 
Analyzing home.xml: 
Analyzing simple.xml: 
Analyzing cd_catalog.xml: 
Analyzing portfolio_xsl.xml: 
Analyzing note_in_dtd.xml: 
Statistical Elements Analysis of 9 xml documents with 34 elements 
CATALOG*2 22% 
    CD*26 50% 
    ARTIST*26 100% 
    COMPANY*26 100% 
    COUNTRY*26 100% 
    PRICE*26 100% 
    TITLE*26 100% 
    YEAR*26 100% 
    PLANT*36 50% 
    AVAILABILITY*36 100% 
    BOTANICAL*36 100% 
    COMMON*36 100% 
    LIGHT*36 100% 
    PRICE*36 100% 
    ZONE*36 100% 
breakfast-menu*1 11% 
    food*5 100% 
    calories*5 100% 
    description*5 100% 
    name*5 100% 
    price*5 100% 
note*3 33% 
    body*1 100% 
    from*1 100% 
    heading*1 100% 
    to*1 100% 
page*1 11% 
    para*1 100% 
    title*1 100% 
portfolio*2 22% 
    stock*2 100% 
    name*2 100% 
    price*2 100% 
    symbol*2 100% 
+0

この挑戦は、元の質問の第2部分を尊重しないで、タグが何回出現するか追跡します。 – tye

+0

私にはわかりません:タグの出現数は '*'の直後です。その要素は、さまざまなXMLファイルに異なるカーディナリティーで表示され、「最小...最大平均」カーディナリティとして表示されます。 xmlcodesのサンプルファイルには当てはまらないので、あなたは '* x'しか持っていません。 – VonC

0

このcode-challenge ...
にルビーで可能な解決策であります私はそれがかなりひどくコード化されていると確信していますが、少なくともそれはJ.パブロ・フェルナンデスの質問に答えるかもしれません。

'.rbファイルにコピー&ペーストしてrubyを呼び出します。インターネットに接続している場合、それは動作します。)

require "rexml/document" 
require "net/http" 
require "iconv" 
include REXML 
class NodeAnalyzer 
    @@fullPathToFilesToSubNodesNamesToCardinalities = Hash.new() 
    @@fullPathsToFiles = Hash.new() #list of files in which a fullPath node is detected 
    @@fullPaths = Array.new # all fullpaths sorted alphabetically 
    attr_reader :name, :father, :subNodesAnalyzers, :indent, :file, :subNodesNamesToCardinalities 
    def initialize(aName="", aFather=nil, aFile="") 
     @name = aName; @father = aFather; @subNodesAnalyzers = []; @file = aFile 
    @subNodesNamesToCardinalities = Hash.new(0) 
    if aFather && !aFather.name.empty? then @indent = " " else @indent = "" end 
    if aFather 
     @indent = @father.indent + self.indent 
     @father.subNodesAnalyzers << self 
     @father.updateSubNodesNamesToCardinalities(@name) 
    end 
    end 
    @@nodesRootAnalyzer = NodeAnalyzer.new 
    def NodeAnalyzer.nodesRootAnalyzer 
    return @@nodesRootAnalyzer 
    end 
    def updateSubNodesNamesToCardinalities(aSubNodeName) 
    aSubNodeCardinality = @subNodesNamesToCardinalities[aSubNodeName] 
    @subNodesNamesToCardinalities[aSubNodeName] = aSubNodeCardinality + 1 
    end 
    def NodeAnalyzer.recordNode(aNodeAnalyzer) 
    if aNodeAnalyzer.fullNodePath.empty? == false 
     if @@fullPaths.include?(aNodeAnalyzer.fullNodePath) == false then @@fullPaths << aNodeAnalyzer.fullNodePath end 
     # record a full path in regard to its xml file (records it only one for a given xlm file) 
     someFiles = @@fullPathsToFiles[aNodeAnalyzer.fullNodePath] 
     if someFiles == nil 
     someFiles = Array.new(); @@fullPathsToFiles[aNodeAnalyzer.fullNodePath] = someFiles; 
     end 
     if !someFiles.include?(aNodeAnalyzer.file) then someFiles << aNodeAnalyzer.file end 
    end 
    #record cardinalties of sub nodes for a given xml file 
    someFilesToSubNodesNamesToCardinalities = @@fullPathToFilesToSubNodesNamesToCardinalities[aNodeAnalyzer.fullNodePath] 
    if someFilesToSubNodesNamesToCardinalities == nil 
     someFilesToSubNodesNamesToCardinalities = Hash.new(); @@fullPathToFilesToSubNodesNamesToCardinalities[aNodeAnalyzer.fullNodePath] = someFilesToSubNodesNamesToCardinalities ; 
    end 
    someSubNodesNamesToCardinalities = someFilesToSubNodesNamesToCardinalities[aNodeAnalyzer.file] 
    if someSubNodesNamesToCardinalities == nil 
     someSubNodesNamesToCardinalities = Hash.new(0); someFilesToSubNodesNamesToCardinalities[aNodeAnalyzer.file] = someSubNodesNamesToCardinalities 
     someSubNodesNamesToCardinalities.update(aNodeAnalyzer.subNodesNamesToCardinalities) 
    else 
     aNodeAnalyzer.subNodesNamesToCardinalities.each() do |aSubNodeName, aCardinality| 
     someSubNodesNamesToCardinalities[aSubNodeName] = someSubNodesNamesToCardinalities[aSubNodeName] + aCardinality 
     end 
    end 
    #puts "someSubNodesNamesToCardinalities for #{aNodeAnalyzer.fullNodePath}: #{someSubNodesNamesToCardinalities}" 
    end 
    def file 
    #if @file.empty? then @father.file else return @file end 
    if @file.empty? then if @father != nil then return @father.file else return '' end else return @file end 
    end 
    def fullNodePath 
    if @father == nil then return '' elsif @father.name.empty? then return @name else return @father.fullNodePath+"/"[email protected] end 
    end 
    def to_s 
    s = "" 
    if @name.empty? == false 
     s = "#{@indent}#{self.fullNodePath} [#{self.file}]\n" 
    end 
    @subNodesAnalyzers.each() do |aSubNodeAnalyzer| 
     s = s + aSubNodeAnalyzer.to_s 
    end 
    return s 
    end 
    def NodeAnalyzer.displayStats(aFullPath="") 
    s = ""; 
    if aFullPath.empty? then s = "Statistical Elements Analysis of #{@@nodesRootAnalyzer.subNodesAnalyzers.length} xml documents with #{@@fullPaths.length} elements\n" end 
    someFullPaths = @@fullPaths.sort 
    someFullPaths.each do |aFullPath| 
     s = s + getIndentedNameFromFullPath(aFullPath) + "*" 
     nbFilesWithThatFullPath = getNbFilesWithThatFullPath(aFullPath); 
     aParentFullPath = getParentFullPath(aFullPath) 
     nbFilesWithParentFullPath = getNbFilesWithThatFullPath(aParentFullPath); 
     aNameFromFullPath = getNameFromFullPath(aFullPath) 
     someFilesToSubNodesNamesToCardinalities = @@fullPathToFilesToSubNodesNamesToCardinalities[aParentFullPath] 
     someCardinalities = Array.new() 
     someFilesToSubNodesNamesToCardinalities.each() do |aFile, someSubNodesNamesToCardinalities| 
     aCardinality = someSubNodesNamesToCardinalities[aNameFromFullPath] 
     if aCardinality > 0 && someCardinalities.include?(aCardinality) == false then someCardinalities << aCardinality end 
     end 
     if someCardinalities.length == 1 
     s = s + someCardinalities.to_s + " " 
     else 
     anAvg = someCardinalities.inject(0) {|sum,value| Float(sum) + Float(value) }/Float(someCardinalities.length) 
     s = s + sprintf('%.1f', anAvg) + " " + someCardinalities.min.to_s + "..." + someCardinalities.max.to_s + " " 
     end 
     s = s + sprintf('%d', Float(nbFilesWithThatFullPath)/Float(nbFilesWithParentFullPath) * 100) + '%' 
     s = s + "\n" 
    end 
    return s 
    end 
    def NodeAnalyzer.getNameFromFullPath(aFullPath) 
    if aFullPath.include?("/") == false then return aFullPath end 
    aNameFromFullPath = aFullPath.dup 
    aNameFromFullPath[/^(?:[^\/]+\/)+/] = "" 
    return aNameFromFullPath 
    end 
    def NodeAnalyzer.getIndentedNameFromFullPath(aFullPath) 
    if aFullPath.include?("/") == false then return aFullPath end 
    anIndentedNameFromFullPath = aFullPath.dup 
    anIndentedNameFromFullPath = anIndentedNameFromFullPath.gsub(/[^\/]+\//, " ") 
    return anIndentedNameFromFullPath 
    end 
    def NodeAnalyzer.getParentFullPath(aFullPath) 
    if aFullPath.include?("/") == false then return "" end 
    aParentFullPath = aFullPath.dup 
    aParentFullPath[/\/[^\/]+$/] = "" 
    return aParentFullPath 
    end 
    def NodeAnalyzer.getNbFilesWithThatFullPath(aFullPath) 
    if aFullPath.empty? 
     return @@nodesRootAnalyzer.subNodesAnalyzers.length 
    else 
     return @@fullPathsToFiles[aFullPath].length; 
    end 
    end 
end 
class REXML::Document 
    def analyze(node, aFatherNodeAnalyzer, aFile="") 
    anNodeAnalyzer = NodeAnalyzer.new(node.name, aFatherNodeAnalyzer, aFile) 
    node.elements.each() do |aSubNode| analyze(aSubNode, anNodeAnalyzer) end 
    NodeAnalyzer.recordNode(anNodeAnalyzer) 
    end 
end 

begin 
    anXmlFilesDirectory = "xmlfiles.com/examples/" 
    anXmlFilesRegExp = Regexp.new("http:\/\/" + anXmlFilesDirectory + "([^\"]*)") 
    a = Net::HTTP.get(URI("http://www.google.fr/search?q=site:"+anXmlFilesDirectory+"+filetype:xml&num=100&as_qdr=all&filter=0")) 
    someXmlFiles = a.scan(anXmlFilesRegExp) 
    someXmlFiles.each() do |anXmlFile| 
    anXmlFileContent = Net::HTTP.get(URI("http://" + anXmlFilesDirectory + anXmlFile.to_s)) 
    anUTF8XmlFileContent = Iconv.conv("ISO-8859-1//ignore", 'UTF-8', anXmlFileContent).gsub(/\s+encoding\s*=\s*\"[^\"]+\"\s*\?/,"?") 
    anXmlDocument = Document.new(anUTF8XmlFileContent) 
    puts "Analyzing #{anXmlFile}: #{NodeAnalyzer.nodesRootAnalyzer.name}" 
    anXmlDocument.analyze(anXmlDocument.root,NodeAnalyzer.nodesRootAnalyzer, anXmlFile.to_s) 
    end 
    NodeAnalyzer.recordNode(NodeAnalyzer.nodesRootAnalyzer) 
    puts NodeAnalyzer.displayStats 
end 
+0

ちょっと短い識別子を使用したばかりの場合は、コードを読むことさえ可能かもしれません。 – JesperE

0

JeniTの答えで行く - 彼女は私が'02に戻ってから習い始め最初のXSLT教祖年代の一つです。 XMLのパワーを本当に理解するには、XPathとXSLTを使用してノードを操作する方法を学ぶ必要があります。

+0

私は同意する、JeniTの答えは正しい道だ...しかし、それは私がちょうど起動し、それが動作するかどうかを確認することができます完全なスクリプトではありません。チャレンジに答えるためのより完全なxsltスクリプトを書いてください) – VonC

+0

Jeniは、スクリプト/プロセスを実行する前にドキュメントを連結していることを前提としています。これで十分だろうか?ファイルをファイルストリームと連結し、XML/XSLT名前空間の内容をロードして実行することができます。 –

+0

実際には、ドキュメントは連結されているとは限りませんが、ドキュメントノードのXPathシーケンスに含まれています。これは 'collection()'を使って最も簡単に生成されます。私は完全なスタイルシートを与えるために上記の私の答えを更新しました。 – JeniT

関連する問題