"""A class to encapsulate ComicRack's ComicInfo.xml data""" # Copyright 2012-2014 Anthony Beville # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import logging import xml.etree.ElementTree as ET from comicapi import utils from comicapi.genericmetadata import GenericMetadata from comicapi.issuestring import IssueString logger = logging.getLogger(__name__) class ComicInfoXml: writer_synonyms = ["writer", "plotter", "scripter"] penciller_synonyms = ["artist", "penciller", "penciler", "breakdowns"] inker_synonyms = ["inker", "artist", "finishes"] colorist_synonyms = ["colorist", "colourist", "colorer", "colourer"] letterer_synonyms = ["letterer"] cover_synonyms = ["cover", "covers", "coverartist", "cover artist"] editor_synonyms = ["editor"] def get_parseable_credits(self): parsable_credits = [] parsable_credits.extend(self.writer_synonyms) parsable_credits.extend(self.penciller_synonyms) parsable_credits.extend(self.inker_synonyms) parsable_credits.extend(self.colorist_synonyms) parsable_credits.extend(self.letterer_synonyms) parsable_credits.extend(self.cover_synonyms) parsable_credits.extend(self.editor_synonyms) return parsable_credits def metadata_from_string(self, string): tree = ET.ElementTree(ET.fromstring(string)) return self.convert_xml_to_metadata(tree) def string_from_metadata(self, metadata, xml=None): tree = self.convert_metadata_to_xml(self, metadata, xml) tree_str = ET.tostring(tree.getroot(), encoding="utf-8", xml_declaration=True).decode() return tree_str def convert_metadata_to_xml(self, filename, metadata, xml=None): # shorthand for the metadata md = metadata if xml: root = ET.ElementTree(ET.fromstring(xml)).getroot() else: # build a tree structure root = ET.Element("ComicInfo") root.attrib["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance" root.attrib["xmlns:xsd"] = "http://www.w3.org/2001/XMLSchema" # helper func def assign(cix_entry, md_entry): if md_entry is not None and md_entry: et_entry = root.find(cix_entry) if et_entry is not None: et_entry.text = str(md_entry) else: ET.SubElement(root, cix_entry).text = str(md_entry) else: et_entry = root.find(cix_entry) if et_entry is not None: et_entry.clear() assign("Title", md.title) assign("Series", md.series) assign("Number", md.issue) assign("Count", md.issue_count) assign("Volume", md.volume) assign("AlternateSeries", md.alternate_series) assign("AlternateNumber", md.alternate_number) assign("StoryArc", md.story_arc) assign("SeriesGroup", md.series_group) assign("AlternateCount", md.alternate_count) assign("Summary", md.comments) assign("Notes", md.notes) assign("Year", md.year) assign("Month", md.month) assign("Day", md.day) # need to specially process the credits, since they are structured # differently than CIX credit_writer_list = [] credit_penciller_list = [] credit_inker_list = [] credit_colorist_list = [] credit_letterer_list = [] credit_cover_list = [] credit_editor_list = [] # first, loop thru credits, and build a list for each role that CIX # supports for credit in metadata.credits: if credit["role"].lower() in set(self.writer_synonyms): credit_writer_list.append(credit["person"].replace(",", "")) if credit["role"].lower() in set(self.penciller_synonyms): credit_penciller_list.append(credit["person"].replace(",", "")) if credit["role"].lower() in set(self.inker_synonyms): credit_inker_list.append(credit["person"].replace(",", "")) if credit["role"].lower() in set(self.colorist_synonyms): credit_colorist_list.append(credit["person"].replace(",", "")) if credit["role"].lower() in set(self.letterer_synonyms): credit_letterer_list.append(credit["person"].replace(",", "")) if credit["role"].lower() in set(self.cover_synonyms): credit_cover_list.append(credit["person"].replace(",", "")) if credit["role"].lower() in set(self.editor_synonyms): credit_editor_list.append(credit["person"].replace(",", "")) # second, convert each list to string, and add to XML struct assign("Writer", utils.list_to_string(credit_writer_list)) assign("Penciller", utils.list_to_string(credit_penciller_list)) assign("Inker", utils.list_to_string(credit_inker_list)) assign("Colorist", utils.list_to_string(credit_colorist_list)) assign("Letterer", utils.list_to_string(credit_letterer_list)) assign("CoverArtist", utils.list_to_string(credit_cover_list)) assign("Editor", utils.list_to_string(credit_editor_list)) assign("Publisher", md.publisher) assign("Imprint", md.imprint) assign("Genre", md.genre) assign("Web", md.web_link) assign("PageCount", md.page_count) assign("LanguageISO", md.language) assign("Format", md.format) assign("AgeRating", md.maturity_rating) assign("CommunityRating", md.community_rating) assign("BlackAndWhite", "Yes" if md.black_and_white else None) assign("Manga", md.manga) assign("Characters", md.characters) assign("Teams", md.teams) assign("Locations", md.locations) assign("ScanInformation", md.scan_info) # loop and add the page entries under pages node pages_node = root.find("Pages") if pages_node is not None: pages_node.clear() else: pages_node = ET.SubElement(root, "Pages") for page_dict in md.pages: page = page_dict if "Image" in page: page["Image"] = str(page["Image"]) page_node = ET.SubElement(pages_node, "Page") page_node.attrib = dict(sorted(page_dict.items())) utils.indent(root) # wrap it in an ElementTree instance, and save as XML tree = ET.ElementTree(root) return tree def convert_xml_to_metadata(self, tree): root = tree.getroot() if root.tag != "ComicInfo": raise "1" def get(name): tag = root.find(name) if tag is None: return None return tag.text md = GenericMetadata() md.series = utils.xlate(get("Series")) md.title = utils.xlate(get("Title")) md.issue = IssueString(utils.xlate(get("Number"))).as_string() md.issue_count = utils.xlate(get("Count"), True) md.volume = utils.xlate(get("Volume"), True) md.alternate_series = utils.xlate(get("AlternateSeries")) md.alternate_number = IssueString(utils.xlate(get("AlternateNumber"))).as_string() md.alternate_count = utils.xlate(get("AlternateCount"), True) md.comments = utils.xlate(get("Summary")) md.notes = utils.xlate(get("Notes")) md.year = utils.xlate(get("Year"), True) md.month = utils.xlate(get("Month"), True) md.day = utils.xlate(get("Day"), True) md.publisher = utils.xlate(get("Publisher")) md.imprint = utils.xlate(get("Imprint")) md.genre = utils.xlate(get("Genre")) md.web_link = utils.xlate(get("Web")) md.language = utils.xlate(get("LanguageISO")) md.format = utils.xlate(get("Format")) md.manga = utils.xlate(get("Manga")) md.characters = utils.xlate(get("Characters")) md.teams = utils.xlate(get("Teams")) md.locations = utils.xlate(get("Locations")) md.page_count = utils.xlate(get("PageCount"), True) md.scan_info = utils.xlate(get("ScanInformation")) md.story_arc = utils.xlate(get("StoryArc")) md.series_group = utils.xlate(get("SeriesGroup")) md.maturity_rating = utils.xlate(get("AgeRating")) md.community_rating = utils.xlate(get("CommunityRating")) tmp = utils.xlate(get("BlackAndWhite")) if tmp is not None and tmp.lower() in ["yes", "true", "1"]: md.black_and_white = True # Now extract the credit info for n in root: if any( [ n.tag == "Writer", n.tag == "Penciller", n.tag == "Inker", n.tag == "Colorist", n.tag == "Letterer", n.tag == "Editor", ] ): if n.text is not None: for name in n.text.split(","): md.add_credit(name.strip(), n.tag) if n.tag == "CoverArtist": if n.text is not None: for name in n.text.split(","): md.add_credit(name.strip(), "Cover") # parse page data now pages_node = root.find("Pages") if pages_node is not None: for page in pages_node: if "Image" in page.attrib: page.attrib["Image"] = int(page.attrib["Image"]) md.pages.append(page.attrib) md.is_empty = False return md def write_to_external_file(self, filename, metadata, xml=None): tree = self.convert_metadata_to_xml(self, metadata, xml) tree.write(filename, encoding="utf-8", xml_declaration=True) def read_from_external_file(self, filename): tree = ET.parse(filename) return self.convert_xml_to_metadata(tree)