Commit 4fce2202 authored by Jerome Chauveau's avatar Jerome Chauveau
Browse files

recherche EAD - débuts.

parent 06cb1e14
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
.ead-search{
    margin-top: 100px;
}

.ead-search-ul {
    list-style-type: none;
    display: inline-block;
}

.ead-search-ul li {
    display: inline-block;
    margin-left:5px;
    border: 1px solid #ccc;
    border-radius: 2px;
    padding: 2px 5px;
    font-size:0.8rem;
}

.ead-search-ul li button {
    display :inline-block;
    border: none;
    background-color: none;
    padding: 5px;
    margin-left: 5px;
    color: red;
    cursor : pointer;
}

.ead-search label{
    display :inline-block;
    min-width: 200px;
}

#ead-search-date-wrap{
    border-top: 1px solid #ccc;
    margin-top:10px;
    padding-top:10px;
}
#ead-search-date-wrap div span{
    display :inline-block;
    min-width: 50px;
}

#ead-search-results {
    border-top: 1px solid #888;
}
 No newline at end of file
+106 −0
Original line number Diff line number Diff line
import {Plugin} from '../../core/ui/js/Plugin.js';



class EadSearchPlugin extends Plugin{
  constructor(name) {
     super(name);
     this.filters = {}
     if(document.getElementById('ead-search-section'))
        this.fetchIndexLists();

  }


  fetchIndexLists(){
    let indexList = document.querySelectorAll('.ead-search-input')

    indexList.forEach((e) => {
        let indexName = e.dataset.index;
        fetch(baseURI + projectId + '/search-index/'+indexName+'.json').then((res) => res.json()).then( (json) => {
                let datalist = document.getElementById(indexName);
                let entries = json.entries
                entries.forEach((entry) => {
                    let option = document.createElement('option');
                    option.value = decodeURIComponent(entry.normal);
                    datalist.appendChild(option);

                })

                document.getElementById(indexName+'-input').addEventListener('input', (event) => {
                        //alert(event.target.value)
                        if(event.inputType === 'insertReplacementText'){
                            this.addIndexFilter(indexName, event.target.value)
                        }

                });
            })


    })
  }

  addIndexFilter(filterType, filterValue){
    if(filterValue.trim() === '')
    return;

    if(!this.filters[filterType])
        this.filters[filterType] = []
    this.filters[filterType].push(filterValue);
    let li = document.createElement('li');
    let rmBtn = document.createElement('button');
    rmBtn.innerHTML = "×"
    li.innerHTML = filterValue;
    li.appendChild(rmBtn);
    document.getElementById(filterType+'-ul').appendChild(li);
    document.getElementById(filterType+'-input').value = '';
    rmBtn.addEventListener('click', (evt) => {
        li.remove();
        this.filters[filterType].splice(this.filters[filterType].indexOf(filterValue), 1);
        if(this.filters[filterType].length ===0 )
            delete this.filters[filterType];
        console.log(this.filters);
    })
  }


  runEadSearch(){
        let query = 'resultats.html';
        let filtered = false;
        Object.keys(this.filters).forEach((k,i) => {
            query+= (i===0 ? '?' : '&') + 'indexes[]=' + k;
            console.log(this.filters[k])
            for(let j = 0 ; j < this.filters[k].length; j++) {
                query+='&'+k+"[]=" + encodeURIComponent(this.filters[k][j])
            }
            filtered = true;
        })

       query = baseURI + projectId + '/' +query;

        //date
        let dateMode = document.querySelector('input[name=ead-search-radio-date]:checked').value
        if(dateMode === 'interval'){
            let from = document.getElementById('ead-search-date-from').value;
            let to = document.getElementById('ead-search-date-to').value;
            if(from && to)
                query+= (!filtered ? "?" : "&") +"from="+from+"&to="+to
        }
        else if (dateMode === 'in'){
            let dateIn = document.getElementById('ead-search-date-in').value;
            query+=(!filtered ? "?" : "&") + "in="+dateIn
        }

       fetch(query).then(res => res.text()).then(text => {
        document.getElementById('ead-search-results').innerHTML = text
       })
  }

  buildFtIndex(){
    //fetch(this.baseURL + )
  }
 }

let eadSearch = new EadSearchPlugin('EadSearchPlugin')
window.eadSearch = eadSearch;
MAX.addPlugin(eadSearch);
+410 −0
Original line number Diff line number Diff line
xquery version "3.0";

module namespace max.plugin.ead_search = 'pddn/max/plugin/ead_search';
import module namespace max.config = 'pddn/max/config' at '../../rxq/config.xqm';
import module namespace max.html = 'pddn/max/html' at '../../rxq/html.xqm';
import module namespace max.cons = 'pddn/max/cons' at '../../rxq/cons.xqm';
import module namespace max.util = 'pddn/max/util' at '../../rxq/util.xqm';
import module namespace max.i18n = 'pddn/max/i18n' at '../../rxq/i18n.xqm';

(:declare variable $max.plugin.search:PLUGIN_ID := "search";:)
(:declare variable $max.plugin.search:ALL_TXT := "all_txt";:)
(:declare variable $max.plugin.search:SELECTION := "selection";:)

(:Plugin parameters  - should be defined in MAX CONFIGURATION FILE:)
(:declare variable $max.plugin.search:TAG_PARAMETER := "tag";:)
(:declare variable $max.plugin.search:BACK_TO_TEXT_ID_PARAMETER := "backToTextID";:)


declare variable $max.plugin.ead_search:INDEX_TAGS :=
    <search>
        <tags>
            <tag name="corpname" attribute="role" value="scriptorium"/>
            <tag name="famname"/>
            <tag name="function"/>
            <tag name="genreform"/>
            <tag name="geogname"/>
            <tag name="occupation"/>
            <tag name="name"/>
            <tag name="persname"/>
            <tag name="subject"/>
            <tag name="title" attribute="type" value="subscriptio"/>
        </tags>
        <dates format="YYYY"></dates>
    </search>;

declare
%rest:GET
%output:method("html")
%rest:path("/{$project}/rechercher.html")
function max.plugin.ead_search:searchPage($project){
    let $dbPath := max.config:getProjectDBPath($project)
    let $datalists := <section id="ead-search-section" class="ead-search">
        {
        for $indexTag in $max.plugin.ead_search:INDEX_TAGS//tag
        let $tagName := string($indexTag/@name)
        return
            if(count(collection($dbPath)//*[fn:local-name()=$tagName and @normal]) > 0)
            then
            <div>
                <label>{$tagName}s</label>
                <input type="text" class="ead-search-input" data-index="{$tagName}" id="{$tagName}-input" list="{$tagName}" multiple="multiple"></input>
                <ul id="{$tagName}-ul" class="ead-search-ul">
                </ul>
                <datalist id="{$tagName}" class="ead-index-list"></datalist>
            </div>
            else ()
        }
            <div id="ead-search-date-wrap">
                <label>Année de production</label>
                <!--<div>
                    <input type="radio" name="ead-search-radio-date" checked="checked" value="in"/>
                    <span>En</span>
                    <input type="number" id="ead-search-date-in"/>
                </div>-->
                <div>
                    <!--display none : l'option "entre" ne suffit-elle pas ? -->
                    <input style="display:none;" type="radio" name="ead-search-radio-date" checked="checked" value="interval"/>
                    <span>Entre</span>
                    <input type="number" id="ead-search-date-from" value="1000"/>
                    <span>et</span>
                    <input type="number" id="ead-search-date-to" value="2000"/>
                </div>
            </div>
            <div><button onclick="window.eadSearch.runEadSearch()">Rechercher</button></div>
            <div id="ead-search-results"></div>
        </section>

    return max.html:render($project, 'search', $datalists)
};



declare
%rest:GET
%output:method("html")
%rest:query-param("indexes[]", "{$indexes}")
%rest:query-param("from", "{$from}")
%rest:query-param("to", "{$to}") (: from date A to date B :)
%rest:query-param("in", "{$in}")(:in date D:)
%rest:path("/{$project}/resultats.html")
function max.plugin.ead_search:runSearch($project as xs:string, $indexes as xs:string*, $from as xs:string?, $to as xs:string?, $in as xs:string?){

    let $dbPath := max.config:getProjectDBPath($project)
    let $queryStart := 'for $c in //*:c '
    let $subqueries := for $index at $i in $indexes
        let $p := request:parameter($index||'[]')
        return
            let $or := string-join(for $n in $p return '@normal="'||$n||'" or contains(./text(),"'||$n||'")',' or ' )
            return
            if($i = 1) then ' where count($c//*[local-name(.)!="c"]//*:'||$index||'['||$or||'])>0 '
            else ' and count($c//*[local-name(.)!="c"]//*:'||$index||'['||$or||'])>0 '

    let $joinedSubQueries := fn:string-join($subqueries,'')
    let $fullQuery := $queryStart || $joinedSubQueries || ' return $c'


(:    let $fullQuery := $queryStart:)
(:                || (if($joinedSubQueries) then ($joinedSubQueries ||' and ') else ' where '):)
(:                || max.plugin.ead_search:buildDateQuery($project, $from, $to, $in)||' return $c':)

(:    fn:string-join($subqueries,'') || ' ' || max.plugin.ead_search:buildDateQuery($project, $from, $to, $in)||' return $c':)
(:    return $fullQuery:)
    let $matches := max.plugin.ead_search:dateFilter(
            $from, $to, $in, xquery:eval($fullQuery, map { '': db:open($dbPath) }))



    let $html:= <ul>{
        for $c in $matches
        return <li>{$c/*:did/*:unitid/text()}</li>
    }
    </ul>

    return <section>
            <h3>Résultats - {count($matches)}</h3>
            <h4>XQuery = {$fullQuery} - (db = {$dbPath})</h4>
            {$html}
        </section>
};


declare %private function max.plugin.ead_search:dateFilter(
        $from as xs:string?,
        $to as xs:string?,
        $in as xs:string?,
        $matches){

    if($from and $to)
    then max.plugin.ead_search:dateIntervalFilter($from, $to, $matches)
    else if($in) then () (:todo:)
    else $matches

};

declare %private function max.plugin.ead_search:dateIntervalFilter($from as xs:string, $to as xs:string, $matches as item()+){
    let $fromDateTime := max.plugin.ead_search:parseDateParameter($from)
    let $toDateTime := max.plugin.ead_search:parseDateParameter($to)

    return
    for $m in $matches
        where $m//*:unitdate[1]
        let $unitDate := string((($m//*:unitdate)[1])/@normal)
        return if(contains($unitDate,'/'))
            then
            let $dates := tokenize($unitDate,'/')
            let $unitFrom :=  max.plugin.ead_search:parseDateParameter($dates[1])
            let $unitTo:=  max.plugin.ead_search:parseDateParameter($dates[2])
            return
               if($fromDateTime <= $unitFrom and $toDateTime >= $unitTo)
               then $m
               else()
            else
                let $unitFrom :=  max.plugin.ead_search:parseDateParameter($unitDate)
                return
                    if($fromDateTime <= $unitFrom) then $m else ()

};

declare function max.plugin.ead_search:buildSimpleDateQuery($project as xs:string, $in as xs:string){

    '2=2'

(:    xs:dateTime(max.plugin.ead_search:parseDateParameter($from)) || ' to ' || xs:dateTime(max.plugin.ead_search:parseDateParameter($to)):)
};

declare
%rest:GET
%output:method("json")
%rest:path("/{$project}/search-index/{$tag}.json")
function max.plugin.ead_search:searchIndexesList($project, $tag){
    let $dbPath := max.config:getProjectDBPath($project)
    let $tagParameters := $max.plugin.ead_search:INDEX_TAGS//tag[@name=$tag]
   (:select all nodes with required tag and attributes :)
    let $allEntries := for $elt in collection($dbPath)//*[fn:local-name()=$tag and @normal]
        where
            if($tagParameters/@attribute) then
                $elt/@*[local-name(.)=string($tagParameters/@attribute) and string(.)=string($tagParameters/@value)]
            else true()
        return $elt

    (:get their normal forms:)
    let $allNormals := for $e in $allEntries return string($e/@normal)

    (:filter : avoid doublons:)
    let $entries := for $normal in distinct-values($allNormals)
                    return $allEntries[@normal=$normal][1]

    let $jsonStr :=
            for $entry in $entries
            order by $entry/@normal
            where fn:string-length($entry/@normal) > 0
            return '{"normal":"' || encode-for-uri($entry/@normal) || '", "tag":"' || $tag || '", "role" :"'||$entry/@role||'"}'
    return json:parse('{"entries" :[' || string-join($jsonStr,',') ||']}')
};


declare %private function max.plugin.ead_search:parseDateParameter($strDate as xs:string){
    let $format := string($max.plugin.ead_search:INDEX_TAGS/dates/@format)
    return try {
     switch ($format)
        case "YYYY"
            return xs:date($strDate||'-01-01')
        case "YYYY-MM"
            return xs:date($strDate||'-01')
        default return xs:date($strDate)
        }
    catch * {
         xs:date('0001-01-01')
        }

};


(:test index:)


declare
%rest:GET
%rest:path("/{$project}/indexes.html")
function max.plugin.ead_search:allLevels($project){
    let $doc := collection(max.config:getProjectDBPath($project))
    let $topLevels := for $i in distinct-values($doc//*:controlaccess[not(ancestor::*:controlaccess)]/descendant::*:subject[1]) order by $i
    return normalize-space($i)
        (:let $trimed := normalize-space($i)
        return
            if ($doc//*:controlaccess/*:controlaccess//*:controlaccess/*:subject[text() contains text {$trimed}])
        then ()
        else $trimed:)

    return
        <root>{
            for $top in $topLevels
            return<list>
                <item>{$top}</item>
                {max.plugin.ead_search:subLevels($doc, $top)}</list>}</root>
};

declare function max.plugin.ead_search:subLevels($doc, $parent){
    let $children := for $ca in $doc//*:controlaccess where $ca/subject[text()=$parent]
    return $ca/*:controlaccess
    return if (count($children) = 0)
    then ()
    else <list>{for $child in distinct-values($children/*:subject) order by $child
        return if(normalize-space($child)="")
        then()
        else<item>{$child}{max.plugin.ead_search:subLevels($doc,$child)}</item>}</list>

};


declare function max.plugin.ead_search:indexTopLevels($project){
    let $doc := collection(max.config:getProjectDBPath($project))
    return
    if(count($doc//*:controlaccess//*:subject) = 0)
    then max.plugin.ead_search:flatIndex($doc)
    else
    let $topLevels := for $i in distinct-values($doc//*:controlaccess[not(ancestor::*:controlaccess)]/descendant::*:subject[1]) order by $i
    return
      let $trimed := normalize-space($i)
      return
        if ($doc//*:controlaccess/*:controlaccess//*:controlaccess/*:subject[text() contains text {$trimed}])
        then ()
        else $trimed
    return
      <root>{
      for $top in $topLevels
        return<list>
            <item nbchildren="{max.plugin.ead_search:countChildren($doc,$top)}">{$top}</item>
            </list>}</root>

};

declare function max.plugin.ead_search:countChildren($doc, $parent){
    let $children := for $ca in $doc//*:controlaccess where $ca/subject[text()=$parent]
    return $ca/*:controlaccess

    return count(for $child in distinct-values($children/*:subject)
    return if(normalize-space($child)="")
    then()
    else<item/>)
};



declare function max.plugin.ead_search:flatIndex($doc){
    let $types := distinct-values($doc//*:controlaccess/*:genreform/@*:type)
    return
        <ul>{
            for $type in $types
            return
                <li class="jstree-closed">{$type}
                    <ul>
                        {
                            let $entries := $doc//*:controlaccess/*:genreform[@*:type=$type]/text()
                            for $entry in distinct-values($entries)
                            return <li><span class='indexEntry'>{$entry}</span><span class='badge'>{count(for $a in $entries where $a = $entry return $a)}</span></li>
                        }
                    </ul>
                </li>
        }
        </ul>
};



(:
inutilisée - on conserve jusqu'aux tests avancés de recherche
:)
declare
%rest:GET
%output:method("html")
%rest:path("/{$project}/search-build-ftindex.html")
function max.plugin.ead_search:buildFtIndexPage($project){

    let $content := <section style="margin: 100px 50px">
        <h2>Indexation pour la recherche</h2>
        <div>
            Tags indexés :  {max.config:getPluginParameterValue($project, 'ead_search','ftIndexTags')}
        </div>
        <div>
            <button onclick='window.eadSearch.buildFtIndex()'>Lancer l'indexation</button>
        </div>
    </section>

    return max.html:render($project,'ftindex',$content)
};

(:
inutilisée - on conserve jusqu'aux tests avancés de recherche
:)
declare
%updating
%rest:POST
%output:method("html")
%rest:path("/{$project}/search-build-ftindex.html")
function max.plugin.ead_search:buildFtIndex($project){
    let $tags := max.config:getPluginParameterValue($project, 'ead_search','ftIndexTags')
    return (
        db:optimize(
                max.config:getProjectDBPath($project),
                false(),
                map {
                'ftindex': true(),
                'ftinclude': $tags }
        ),
        update:output('<html>FT index for <em>'||$tags||'</em> successfull</html>'))
};



(:
Clés d'indexation en Pleade

|:--------------------------|:----------|:---------------------|:---------------------------|
|Utilité                    |Élément    |attribut de typologie |attribut de normalisation   |
|:--------------------------|:----------|:---------------------|:---------------------------|
|Collectivité               |corpname   |@role                 |@normal                     |
|Nom de famille             |famname    |@role                 |@normal                     |
|Activité                   |function   |ø                     |@normal                     |
|Typologie documentaire     |genreform  |@type                 |@normal                     |
|Lieu                       |geogname   |@role                 |@normal                     |
|Fonction                   |occupation |ø                     |@normal                     |
|Nom                        |name       |@role                 |@normal                     |
|Personne                   |pername    |@role                 |@normal                     |
|Sujet                      |subject    |ø                     |@normal                     |
|Titre                      |title      |@type                 |@normal                     |
|:--------------------------|:----------|:---------------------|:---------------------------|
|Date                       |date       |@type                 |@normal                     |
|Date d’un c et ses enfants |unitdate   |@type                 |@normal                     |
|:--------------------------|:----------|:---------------------|:---------------------------|

:)


(:
https://redmine.unicaen.fr/Etablissement/boards/70/topics/200
:)

(:

declare
%rest:GET
%rest:path("/{$projectId}/{$xmlDocName}/famname/unique.xml")
function max.plugin.json_idx:famname($projectId, $xmlDocName){
let $dbPath := max.config:getProjectDBPath($projectId)
let $content := for $n in sort(distinct-values(doc(concat($dbPath, '/', $xmlDocName))//*:famname))
return <item>{$n}</item>
return <items>{$content}</items>
};
declare
%rest:GET
%rest:path("/{$projectId}/{$xmlDocName}/corpname/unique.xml")
function max.plugin.json_idx:corpname($projectId, $xmlDocName){
let $dbPath := max.config:getProjectDBPath($projectId)
let $content := for $n in sort(distinct-values(doc(concat($dbPath, '/', $xmlDocName))//*:corpname))
return <item>{$n}</item>
return <items>{$content}</items>
};

:)
 No newline at end of file