2 # -*- coding: utf-8 -*-
4 # (c) Copyright 2008-9 Hewlett-Packard Development Company, L.P.
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 __title__ = 'DAT to DRV.IN converter. Also creates Foomatic XML files.'
25 __doc__ = "Create DRV.IN file and Foomatic XML files from MODELS.DAT data. Processes all *.in.template files in prnt/drv directory."
28 os.putenv("HPLIP_BUILD", "1")
35 from xml.dom.minidom import Document, parse, parseString
36 from types import StringType, UnicodeType
41 from base import utils, tui, models
42 #from prnt import printable_areas
50 norm_models = {} # { 'norm'd model' : ( 'model', type, has_scanner ), ... }
54 sorted_category_models = {}
55 unsupported_models = []
57 pat_prod_num = re.compile("""(\d+)""", re.I)
58 pat_template = re.compile("""^(\s*)//\s*<%(\S+)%>""", re.I)
59 pat_template2 = re.compile("""^\s*<%(\S+)%>""", re.I)
62 SHORTENING_REPLACEMENTS = {
63 'color laserjet' : 'CLJ',
67 'color inkjet printer' : '',
69 'business inkjet' : 'BIJ',
70 'designjet' : 'DESIGNJ',
71 'printer scanner copier' : 'PSC',
73 'professional' : 'Pro',
77 USAGE = [(__doc__, "", "name", True),
78 ("Usage: dat2drv.py [OPTIONS]", "", "summary", True),
80 ("Verbose mode:", "-v or --verbose", "option", False),
81 ("Quiet mode:", "-q or --quiet", "option", False),
82 utils.USAGE_LOGGING1, utils.USAGE_LOGGING2,
86 def usage(typ='text'):
88 utils.log_title(__title__, __version__)
90 utils.format_text(USAGE, typ, __title__, 'drv2xml.py', __version__)
96 if isinstance(v, UnicodeType):
103 def __init__(self, doc, el):
107 def __getitem__(self, name):
108 a = self.el.getAttributeNode(name)
110 return _encode(a.value)
113 def __setitem__(self, name, value):
114 self.el.setAttribute(name, _encode(value))
116 def __delitem__(self, name):
117 self.el.removeAttribute(name)
120 return _encode(self.doc.toxml())
123 return _encode(self.doc.toxml())
126 return XMLElement(self.doc, el)
128 def get(self, name, default=None):
129 a = self.el.getAttributeNode(name)
131 return _encode(a.value)
132 return _encode(default)
134 def add(self, tag, **kwargs):
135 el = self.doc.createElement(tag)
136 for k, v in kwargs.items():
137 el.setAttribute(k, _encode(str(v)))
138 return self._inst(self.el.appendChild(el))
140 def addText(self, data):
143 self.doc.createTextNode(_encode(data))))
145 def addComment(self, data):
148 self.doc.createComment(data)))
150 def getText(self, sep=" "):
152 for node in self.el.childNodes:
153 if node.nodeType == node.TEXT_NODE:
155 return _encode(string.join(rc, sep))
157 def getAll(self, tag):
158 return map(self._inst, self.el.getElementsByTagName(tag))
161 class XMLDocument(XMLElement):
163 def __init__(self, tag=None, **kwargs):
164 self.doc = Document()
165 XMLElement.__init__(self, self.doc, self.doc)
167 self.el = self.add(tag, **kwargs).el
170 self.doc = self.el = parse(d)
173 def parseString(self, d):
174 self.doc = self.el = parseString(_encode(d))
180 def fixFileName(model):
181 if model.startswith('hp_'):
182 model = model.replace('hp_', 'hp-')
184 elif model.startswith('apollo_'):
185 model = model.replace('apollo_', 'apollo-')
187 elif not model.startswith('hp-'):
188 model = 'hp-' + model
190 return model.strip('~')
194 is_aio = (models_dict[m]['scan-type'] != SCAN_TYPE_NONE or
195 models_dict[m]['copy-type'] != COPY_TYPE_NONE or
196 models_dict[m]['fax-type'] != FAX_TYPE_NONE)
198 if "deskjet" in m or \
199 ("color" in m and "inkjet" in m) or \
200 m.startswith("dj") or \
204 i = MODEL_TYPE2_DESKJET_AIO
206 i = MODEL_TYPE2_DESKJET
208 elif "photosmart" in m:
209 i = MODEL_TYPE2_PHOTOSMART
211 elif "officejet" in m:
212 i = MODEL_TYPE2_OFFICEJET
215 "printer_scanner_copier" in m:
219 elif "laserjet" in m:
221 i = MODEL_TYPE2_COLOR_LASERJET
223 i = MODEL_TYPE2_LASERJET
226 i = MODEL_TYPE2_LASERJET
228 elif "business" in m and \
233 elif "edgeline" in m:
234 i = MODEL_TYPE2_EDGELINE
237 i = MODEL_TYPE2_APOLLO
239 elif "designjet" in m or \
243 "electrostatic" in m or \
244 m.startswith('hp_2') or \
245 m.startswith('hp_7') or \
246 m.startswith('hp_9'):
248 i = MODEL_TYPE2_DESIGNJET
251 i = MODEL_TYPE2_OTHER
253 return (m, i, models_dict[m])
256 def sort_product(x, y):
258 _x = int(pat_prod_num.search(x).group(1))
259 except (TypeError, AttributeError):
263 _y = int(pat_prod_num.search(y).group(1))
264 except (TypeError, AttributeError):
267 if not _x and not _y:
273 def sort_product2(x, y): # sort key is first element of tuple
274 return sort_product(x[0], y[0])
277 def load_models(unreleased=True):
280 global norm_models_keys
283 global sorted_category_models
284 global unsupported_models
286 models_dict = model_dat.read_all_files(unreleased)
288 log.debug("Raw models:")
290 for m in models_dict:
291 nm = models.normalizeModelUIName(m)
292 models_dict[m]['norm_model'] = nm.strip('~')
293 models_dict[m]['case_models'] = []
295 i, case_models = 1, []
298 cm = models.normalizeModelUIName(models_dict[m]['model%d' % i])
302 case_models.append(cm)
308 models_dict[m]['case_models'] = case_models[:]
310 models_dict[m]['category'] = cat
312 for c in case_models:
315 if models_dict[m]['support-type'] == SUPPORT_TYPE_NONE:
316 unsupported_models.append((c, m))
318 norm_models_keys = norm_models.keys()
319 norm_models_keys.sort(lambda x, y: sort_product(x, y))
321 unsupported_models.sort(lambda x, y: sort_product2(x, y))
323 total_models = len(norm_models)
325 #log.info("Loaded %d models." % total_models)
334 log.set_module("dat2drv.py")
335 cur_path = os.path.realpath(os.path.normpath(os.getcwd()))
336 dat_path = os.path.join(cur_path, 'data', 'models')
337 model_dat = models.ModelData(dat_path)
346 opts, args = getopt.getopt(args, 'd:l:ho:vq',
348 'help-rest', 'help-man',
351 except getopt.GetoptError, e:
357 if os.getenv("HPLIP_DEBUG"):
358 log.set_level('debug')
361 if o in ('-h', '--help'):
364 elif o == '--help-rest':
367 elif o == '--help-man':
370 elif o in ('-v', '--verbose'):
373 elif o in ('-q', '--quiet'):
376 elif o in ('-l', '--logging'):
377 log.set_level(a.lower().strip())
381 utils.log_title(__title__, __version__)
383 drv_dir = os.path.join(cur_path, 'prnt', 'drv')
389 for template_file in utils.walkFiles(drv_dir, recurse=False, abs_paths=True,
390 return_folders=False, pattern='*.in.template'):
392 basename = os.path.basename(template_file).split('.')[0]
395 drv_in_file = os.path.join(cur_path, 'prnt', 'drv', '%s.drv.in' % basename)
397 # XML output (per model)
398 output_path = os.path.join(cur_path, 'prnt', 'drv', 'foomatic_xml', basename)
400 # XML Output (master driver list)
401 driver_path = os.path.join(cur_path, 'prnt', 'drv', 'foomatic_xml', basename, '%s.xml' % basename)
403 log.info("Working on %s file..." % basename)
404 log.info("Input file: %s" % template_file)
405 log.info("Output file: %s" % drv_in_file)
406 log.info("Output XML directory: %s" % output_path)
407 log.info("Output driver XML file: %s" % driver_path)
413 log.info("Processing %s.drv.in.template..." % basename)
416 template_classes = []
418 template_file_f = open(template_file, 'r')
419 drv_in_file_f = open(drv_in_file, 'w')
421 models_placement = {}
422 for m in models_dict:
423 models_placement[m] = 0
427 for x in template_file_f:
433 drv_in_file_f.write(x)
434 match = pat_template.match(x)
435 if match is not None:
437 indent = match.group(1)
438 indent2 = ' '*(len(indent)+2)
440 classes = match.group(2).split(':')
441 tech_class = classes[0]
443 if tech_class not in models.TECH_CLASSES:
444 errors.append("(%s:line %d) Invalid tech-class (%s): %s" % (basename, line, tech_class, x.strip()))
447 template_classes.append(tech_class)
449 tech_subclass = classes[1:]
452 for sc in tech_subclass:
453 if sc not in models.TECH_SUBCLASSES:
454 errors.append("(%s:line %d) Invalid tech-subclass (%s): %s" % (basename, line, sc, x.strip()))
460 for m in models_dict:
463 if tech_class in models_dict[m]['tech-class'] and \
464 len(models_dict[m]['tech-subclass']) == len(tech_subclass):
466 for msc in models_dict[m]['tech-subclass']:
467 if msc not in tech_subclass:
473 models_placement[m] += 1
477 matches.sort(lambda x, y: sort_product(x, y))
482 log.info("(%s) Adding section for model: %s" % (basename, p))
484 drv_in_file_f.write("%s{\n" % indent)
486 if basename == 'hpcups':
487 model_name = models_dict[p]['norm_model']
489 model_name = models_dict[p]['norm_model'] + " %s" % basename
491 orig_model_name = model_name
494 if len(model_name) > 31:
495 for k in SHORTENING_REPLACEMENTS:
496 if k in model_name.lower():
497 model_name = utils.ireplace(model_name, k, SHORTENING_REPLACEMENTS[k])
498 model_name = model_name.replace(' ', ' ')
500 if len(model_name) < 32:
501 warns.append('len("%s")>31, shortened to len("%s")=%d using sub-brand shortening replacements.' % (orig_model_name, model_name, len(model_name)))
504 if len(model_name) < 32:
507 if "series" in model_name.lower():
508 model_name = utils.ireplace(model_name, "series", "Ser.")
509 model_name = model_name.replace(' ', ' ')
511 if len(model_name) < 32:
512 warns.append('len("%s")>31, shortened to len("%s")=%d using "series" to "ser." replacement.' % (orig_model_name, model_name, len(model_name)))
515 if "ser." in model_name.lower():
516 model_name = utils.ireplace(model_name, "ser.", "")
517 model_name = model_name.replace(' ', ' ')
519 if len(model_name) < 32:
520 warns.append('len("%s")>31, shortened to len("%s")=%d using "ser." removal.' % (orig_model_name, model_name, len(model_name)))
523 if len(model_name) > 31:
524 model_name = model_name[:31]
525 errors.append('len("%s")>31 chars, could not shorten to <32. Truncating to 31 chars (%s).' % (orig_model_name, model_name))
529 drv_in_file_f.write('%sModelName "%s"\n' % (indent2, orig_model_name))
531 if len(models_dict[p]['tech-class']) > 1:
532 drv_in_file_f.write('%sAttribute "NickName" "" "%s %s, $Version' %
533 (indent2, orig_model_name, models.TECH_CLASS_PDLS[tech_class]))
535 drv_in_file_f.write('%sAttribute "NickName" "" "%s, $Version' %
536 (indent2, orig_model_name))
538 if models_dict[p]['plugin'] in (1, 2):
539 if (models_dict[p]['plugin-reason'] & 15 ) in (1, 2, 3, 4, 5, 6, 8, 9, 10, 12):
540 drv_in_file_f.write(', requires proprietary plugin')
542 drv_in_file_f.write('"\n')
544 drv_in_file_f.write('%sAttribute "ShortNickName" "" "%s"\n' % (indent2, model_name))
546 pp = p.replace('_', ' ')
547 if 'apollo' in p.lower():
548 devid = "MFG:Apollo;MDL:%s;DES:%s;" % (pp, pp)
550 devid = "MFG:HP;MDL:%s;DES:%s;" % (pp, pp)
552 drv_in_file_f.write('%sAttribute "1284DeviceID" "" "%s"\n' % (indent2, devid))
554 if len(models_dict[p]['tech-class']) > 1:
555 if basename == 'hpcups':
556 drv_in_file_f.write('%sPCFileName "%s-%s.ppd"\n' %
557 (indent2, fixFileName(p), models.TECH_CLASS_PDLS[tech_class]))
559 drv_in_file_f.write('%sPCFileName "%s-%s-%s.ppd"\n' %
560 (indent2, fixFileName(p), basename, models.TECH_CLASS_PDLS[tech_class]))
562 elif tech_class != 'Postscript':
563 if basename == 'hpcups':
564 drv_in_file_f.write('%sPCFileName "%s.ppd"\n' % (indent2, fixFileName(p)))
566 drv_in_file_f.write('%sPCFileName "%s-%s.ppd"\n' % (indent2, fixFileName(p), basename))
569 drv_in_file_f.write('%sPCFileName "%s-ps.ppd"\n' % (indent2, fixFileName(p)))
571 for c in models_dict[p]['case_models']:
572 drv_in_file_f.write('%sAttribute "Product" "" "(%s)"\n' % (indent2, c))
574 drv_in_file_f.write("%s}\n" % indent)
577 errors.append("(%s:line %d) No models matched the specified classes on line: %s" % (basename, line, x.strip()))
580 match = pat_template2.match(x)
581 if match is not None:
582 errors.append("(%s:line %d) Malformed line: %s (missing initial //)" % (basename, line, x.strip()))
585 template_file_f.close()
586 drv_in_file_f.close()
587 tui.cleanup_spinner()
589 for tc in models.TECH_CLASSES:
590 if tc.lower() in ('undefined', 'postscript', 'unsupported'):
593 if tc not in template_classes:
594 warns.append("(%s) Section <%%%s:...%%> not found." % (basename, tc))
599 if not os.path.exists(output_path):
600 os.makedirs(output_path)
602 if os.path.exists(driver_path):
603 os.remove(driver_path)
606 for f in utils.walkFiles(output_path, recurse=True, abs_paths=True, return_folders=False, pattern='*'):
607 files_to_delete.append(f)
609 for f in files_to_delete:
612 driver_f = file(driver_path, 'w')
614 driver_doc = XMLDocument("driver", id="driver/hplip")
615 name_node = driver_doc.add("name")
616 name_node.addText("hplip")
617 url_node = driver_doc.add("url")
618 url_node.addText("http://hplipopensource.com")
619 supplier_node = driver_doc.add("supplier")
620 supplier_node.addText("Hewlett-Packard")
621 mfg_node = driver_doc.add("manufacturersupplied")
622 mfg_node.addText("HP|Apollo")
623 lic_node = driver_doc.add("license")
624 lic_node.addText("BSD/GPL/MIT")
625 driver_doc.add("freesoftware")
626 support_node = driver_doc.add("supportcontact", level="voluntary", url="https://launchpad.net/hplip")
627 support_node.addText("HPLIP Support at Launchpad.net")
628 shortdesc_node = driver_doc.add("shortdescription")
629 shortdesc_en_node = shortdesc_node.add("en")
630 shortdesc_en_node.addText("HP's driver suite for printers and multi-function devices")
631 func_node = driver_doc.add("functionality")
632 maxresx_node = func_node.add("maxresx")
633 maxresx_node.addText("1200")
634 maxresy_node = func_node.add("maxresy")
635 maxresy_node.addText("1200")
636 func_node.add("color")
637 exec_node = driver_doc.add("execution")
638 exec_node.add("nopjl")
640 proto_node = exec_node.add("prototype")
641 #proto_node.addText("gs -q -dBATCH -dPARANOIDSAFER -dQUIET -dNOPAUSE -sDEVICE=ijs -sIjsServer=hpijs%A%B%C -dIjsUseOutputFD%Z -sOutputFile=- -")
642 comments_node = driver_doc.add("comments")
643 comments_en_node = comments_node.add("en")
644 comments_en_node.addText("")
646 printers_node = driver_doc.add("printers")
648 for m in models_dict:
650 if models_dict[m]['support-type'] == SUPPORT_TYPE_NONE:
653 if 'apollo' in m.lower():
658 if 'apollo' in m.lower():
659 ieee1284 = "MFG:Apollo;MDL:%s;DES:%s;" % (m, m)
662 ieee1284 = "MFG:HP;MDL:%s;DES:%s;" % (m, m)
665 if 'Postscript' in models_dict[m]['tech-class']:
666 postscriptppd = "%s-ps.ppd" % fixFileName(m)
670 if stripped_model.startswith('hp_'):
671 stripped_model = stripped_model.replace('hp_', '').capitalize()
673 elif stripped_model.startswith('apollo_'):
674 stripped_model = stripped_model.replace('apollo_', '').capitalize()
676 fixed_model = stripped_model.replace('_', ' ').capitalize()
678 # Output to the per-model XML file
679 outputModel(m, fixed_model, stripped_model, make, postscriptppd, ieee1284, output_path, verbose)
681 # Output to driver master XML file
682 outputDriver(m, fixed_model, stripped_model, make, printers_node, verbose)
684 driver_f.write(str(driver_doc))
687 # Make sure all models ended up in drv.in file
688 log.info("Checking for errors...")
691 for m in models_dict:
693 tc = models_dict[m]['tech-class']
694 st = models_dict[m]['support-type']
696 if not tc or 'Undefined' in tc:
698 errors.append('(%s) Invalid tech-class for model %s ("Undefined" or missing)' % (basename, m))
700 # warns.append('(%s) Invalid tech-class for unsupported model %s ("Undefined" or missing)' % (basename, m))
703 if not models_placement[m] and st and \
704 len(tc) == 1 and 'Postscript' not in tc:
707 for tc in models_dict[m]['tech-class']:
708 for sc in models_dict[m]['tech-subclass']:
711 errors.append("(%s) Model '%s' did not have a matching section. Needed section: <%%%s:%s%%>" %
712 (basename, m, tc, ':'.join(sects)))
714 if len(tc) == 1 and 'Postscript' in tc:
715 notes.append("(%s) Postscript-only model %s was not included in DRV file." % (basename, m))
717 tui.cleanup_spinner()
721 if not quiet or verbose:
728 tui.header("WARNINGS")
739 log.warn("%d warnings" % len(warns))
742 log.error("%d errors" % len(errors))
745 def parseDeviceID(device_id):
747 x = [y.strip() for y in device_id.strip().split(';') if y]
752 d.setdefault(y[0].strip(), y[1])
754 d.setdefault(y[0].strip(), None)
756 d.setdefault('MDL', '')
757 d.setdefault('SN', '')
760 d['MDL'] = d['MODEL']
764 d['SN'] = d['SERIAL']
771 if d['SN'].startswith('X'):
777 def outputModel(model, fixed_model, stripped_model, make, postscriptppd, ieee1284, output_path, verbose=False):
783 ## fixed_model = model.replace(' ', '_')
785 ## if fixed_model.startswith('hp_'):
786 ## fixed_model = fixed_model.replace('hp_', 'hp-')
788 ## elif fixed_model.startswith('apollo_'):
789 ## fixed_model = fixed_model.replace('apollo_', 'apollo-')
792 ## fixed_model = 'hp-' + fixed_model
794 ## stripped_model = model
795 ## if stripped_model.startswith('hp '):
796 ## stripped_model = stripped_model.replace('hp ', '')
799 printerID = make + '-' + stripped_model
801 output_filename = os.path.join(output_path, printerID+".xml")
804 log.info("\n\n%s:" % output_filename)
806 output_f = file(output_filename, 'w')
808 doc = XMLDocument("printer", id="printer/%s" % printerID)
809 make_node = doc.add("make")
810 make_node.addText(make)
811 model_node = doc.add("model")
812 model_node.addText(fixed_model)
813 url_node = doc.add("url")
814 url_node.addText("http://www.hp.com")
816 lang_node = doc.add("lang")
817 lang_node.add("pcl", level="3")
819 autodetect_node = doc.add("autodetect")
820 usb_node = autodetect_node.add("usb")
822 driver_node = doc.add("driver")
823 driver_node.addText('hplip')
825 drivers_node = doc.add("drivers")
826 driver_node = drivers_node.add("driver")
827 id_node = driver_node.add("id")
828 id_node.addText("hplip")
832 lang_node.add("postscript", level="2")
834 text_node = lang_node.add("text")
835 charset_node = text_node.add("charset")
836 charset_node.addText("us-ascii")
837 #ppd_node = driver_node.add("ppd")
838 #ppd_node.addText(postscriptppd)
840 # id_node.addText("hpijs")
843 #ieee1284_node = usb_node.add("ieee1284")
844 #ieee1284_node.addText(ieee1284)
846 device_id = parseDeviceID(ieee1284)
848 desc_node = usb_node.add("description")
849 #desc_node.addText(device_id['DES'])
850 desc_node.addText(make + ' ' + fixed_model)
852 mfg_node = usb_node.add("manufacturer")
853 #mfg_node.addText(device_id['MFG'])
854 mfg_node.addText("Hewlett-Packard")
856 model_node = usb_node.add("model")
857 #model_node.addText(device_id['MDL'])
858 model_node.addText(make + ' ' + fixed_model)
860 #cmdset_node = usb_node.add("commandset")
861 #cmdset_node.addText("???")
866 output_f.write(str(doc))
871 def outputDriver(m, fixed_model, stripped_model, make, printers_node, verbose):
873 printerID = make + '-' + stripped_model
875 tech_classes = models_dict[m]['tech-class']
877 printer_node = printers_node.add("printer")
878 id_node = printer_node.add("id")
879 id_node.addText("printer/%s" % printerID)
881 ## margins_node = printer_node.add("margins")
882 ## general_margins_node = margins_node.add("general")
884 ## unit_node = general_margins_node.add("unit")
885 ## unit_node.addText("in")
887 ## for tc in tech_classes:
888 ## if tc not in ('Undefined', 'Unsupported', 'PostScript'):
890 ## margins_data = printable_areas.data[tc]
894 ## print margins_data
898 ## <id>printer/HP-DeskJet_350C</id><!-- HP DeskJet 350C -->
900 ## <maxresx>600</maxresx>
901 ## <maxresy>300</maxresy>
904 ## *DefaultResolution: 600dpi
911 ## <right>0.25</right>
913 ## <bottom>0.67</bottom>
915 ## <exception PageSize="A4">
916 ## <left>0.135</left>
917 ## <right>0.135</right>
924 if __name__ == '__main__':
925 sys.exit(main(sys.argv[1:]))