|  | @@ -6,15 +6,124 @@
 | 
	
		
			
				|  |  |  namespace level {
 | 
	
		
			
				|  |  |    export interface Line {
 | 
	
		
			
				|  |  |      kanji: string,
 | 
	
		
			
				|  |  | -    kana: string
 | 
	
		
			
				|  |  | +    kana: string,
 | 
	
		
			
				|  |  | +    start?: number,
 | 
	
		
			
				|  |  | +    end?: number
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    export interface Level {
 | 
	
		
			
				|  |  | +    name: string,
 | 
	
		
			
				|  |  | +    creator?: string,
 | 
	
		
			
				|  |  | +    genre?: string,
 | 
	
		
			
				|  |  | +    difficulty?: string,
 | 
	
		
			
				|  |  | +    audio?: string,
 | 
	
		
			
				|  |  |      lines: Line[]
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  export function loadFromJson(url: string): Promise<Level> {
 | 
	
		
			
				|  |  | +  export interface LevelSet {
 | 
	
		
			
				|  |  | +    name: string,
 | 
	
		
			
				|  |  | +    levels: Level[]
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  export function loadFromJson(url: string): Promise<LevelSet[]> {
 | 
	
		
			
				|  |  |      return window.fetch(url)
 | 
	
		
			
				|  |  |        .then(response => response.json())
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  let parser = new DOMParser();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function parseXML(response: Response): Promise<Document> {
 | 
	
		
			
				|  |  | +    return response.text().then(text => {
 | 
	
		
			
				|  |  | +      let normalized = text.replace(/[“”]/g, '"');
 | 
	
		
			
				|  |  | +      return parser.parseFromString(normalized, "text/xml");
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  export function loadFromTM(base: string): Promise<LevelSet[]> {
 | 
	
		
			
				|  |  | +    return window.fetch(base+'/folderlist.xml')
 | 
	
		
			
				|  |  | +      .then(parseXML)
 | 
	
		
			
				|  |  | +      .then(dom => parseTMFolderList(base, dom))
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function parseTMFolderList(base: string, dom: Document): Promise<LevelSet[]> {
 | 
	
		
			
				|  |  | +    let folderList = dom.querySelectorAll('folder');
 | 
	
		
			
				|  |  | +    let promises = [];
 | 
	
		
			
				|  |  | +    for (let i = 0; i < folderList.length; ++i) {
 | 
	
		
			
				|  |  | +      let folder = folderList[i];
 | 
	
		
			
				|  |  | +      let name = folder.getAttribute('name');
 | 
	
		
			
				|  |  | +      let path = folder.getAttribute('path');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      let promise = window.fetch(base+'/'+path)
 | 
	
		
			
				|  |  | +        .then(parseXML)
 | 
	
		
			
				|  |  | +        .then(dom => parseTMFolder(base, name, dom))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      promises.push(promise);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return Promise.all(promises);
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function parseTMFolder(base: string, name: string, dom: Document): Promise<LevelSet> {
 | 
	
		
			
				|  |  | +    let musicList = dom.querySelectorAll('musicinfo');
 | 
	
		
			
				|  |  | +    let promises = [];
 | 
	
		
			
				|  |  | +    for (let i = 0; i < musicList.length; ++i) {
 | 
	
		
			
				|  |  | +      let musicInfo = musicList[i];
 | 
	
		
			
				|  |  | +      let xmlPath = base+'/'+musicInfo.getAttribute('xmlpath');
 | 
	
		
			
				|  |  | +      let audioPath = base+'/'+musicInfo.getAttribute('musicpath');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      function getData(tag: string): string | null {
 | 
	
		
			
				|  |  | +        let elem = musicInfo.querySelector(tag);
 | 
	
		
			
				|  |  | +        if (elem === null) {
 | 
	
		
			
				|  |  | +          return null;
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          return elem.textContent;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      let name = getData('musicname');
 | 
	
		
			
				|  |  | +      let creator = getData('artist');
 | 
	
		
			
				|  |  | +      let genre = getData('genre');
 | 
	
		
			
				|  |  | +      let difficulty = getData('level');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      let promise = window.fetch(xmlPath)
 | 
	
		
			
				|  |  | +        .then(parseXML)
 | 
	
		
			
				|  |  | +        .then(parseTMSong)
 | 
	
		
			
				|  |  | +        .then(lines => {
 | 
	
		
			
				|  |  | +          return {
 | 
	
		
			
				|  |  | +            name,
 | 
	
		
			
				|  |  | +            creator,
 | 
	
		
			
				|  |  | +            genre,
 | 
	
		
			
				|  |  | +            difficulty,
 | 
	
		
			
				|  |  | +            audio: audioPath,
 | 
	
		
			
				|  |  | +            lines
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        })
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      promises.push(promise);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return Promise.all(promises)
 | 
	
		
			
				|  |  | +      .then(levels => {
 | 
	
		
			
				|  |  | +        return { name, levels }
 | 
	
		
			
				|  |  | +      })
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  function parseTMSong(dom: Document): Line[] {
 | 
	
		
			
				|  |  | +    let kanjiList = dom.querySelectorAll('nihongoword');
 | 
	
		
			
				|  |  | +    let kanaList = dom.querySelectorAll('word');
 | 
	
		
			
				|  |  | +    let intervalList = dom.querySelectorAll('interval');
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    let lines: Line[] = [];
 | 
	
		
			
				|  |  | +    let time = 0;
 | 
	
		
			
				|  |  | +    for (let i = 0; i < intervalList.length; ++i) {
 | 
	
		
			
				|  |  | +      let start = time;
 | 
	
		
			
				|  |  | +      time += parseInt(intervalList[i].textContent) / 1000
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +      lines.push({
 | 
	
		
			
				|  |  | +        kanji: kanjiList[i].textContent,
 | 
	
		
			
				|  |  | +        kana: kanaList[i].textContent,
 | 
	
		
			
				|  |  | +        start: start,
 | 
	
		
			
				|  |  | +        end: time
 | 
	
		
			
				|  |  | +      })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    return lines;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  |  }
 |